diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index ec0e5b9e5a..708589c32f 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -42,8 +42,8 @@ const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; int hifiSockAddrMeta = qRegisterMetaType("HifiSockAddr"); AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool, - QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort, - quint16 assignmentMonitorPort) : + quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, + quint16 assignmentServerPort, quint16 assignmentMonitorPort) : _assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME) { LogUtils::init(); @@ -53,7 +53,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri auto addressManager = DependencyManager::set(); // create a NodeList as an unassigned client, must be after addressManager - auto nodeList = DependencyManager::set(NodeType::Unassigned); + auto nodeList = DependencyManager::set(NodeType::Unassigned, listenPort); auto animationCache = DependencyManager::set(); auto entityScriptingInterface = DependencyManager::set(); @@ -95,6 +95,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri } _assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true); + _assignmentServerSocket.setObjectName("AssigmentServer"); nodeList->setAssignmentServerSocket(_assignmentServerSocket); qDebug() << "Assignment server socket is" << _assignmentServerSocket; @@ -119,6 +120,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri // did we get an assignment-client monitor port? if (assignmentMonitorPort > 0) { _assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort); + _assignmentClientMonitorSocket.setObjectName("AssignmentClientMonitor"); qDebug() << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket; diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 73eaa3dc9f..9d2c816861 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -23,6 +23,7 @@ class AssignmentClient : public QObject { Q_OBJECT public: AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool, + quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort, quint16 assignmentMonitorPort); ~AssignmentClient(); diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 2edae340c3..3b9f8af868 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -59,6 +59,10 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : const QCommandLineOption poolOption(ASSIGNMENT_POOL_OPTION, "set assignment pool", "pool-name"); parser.addOption(poolOption); + + const QCommandLineOption portOption(ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION, + "UDP port for this assignment client (or monitor)", "port"); + parser.addOption(portOption); const QCommandLineOption walletDestinationOption(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION, "set wallet destination", "wallet-uuid"); @@ -158,12 +162,18 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : // check for an overriden assignment server port quint16 assignmentServerPort = DEFAULT_DOMAIN_SERVER_PORT; if (argumentVariantMap.contains(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) { - assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toString().toUInt(); + assignmentServerPort = argumentVariantMap.value(CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION).toUInt(); } if (parser.isSet(assignmentServerPortOption)) { assignmentServerPort = parser.value(assignmentServerPortOption).toInt(); } + + // check for an overidden listen port + quint16 listenPort = 0; + if (argumentVariantMap.contains(ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION)) { + listenPort = argumentVariantMap.value(ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION).toUInt(); + } if (parser.isSet(numChildsOption)) { if (minForks && minForks > numForks) { @@ -185,12 +195,12 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : if (numForks || minForks || maxForks) { AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks, requestAssignmentType, assignmentPool, - walletUUID, assignmentServerHostname, + listenPort, walletUUID, assignmentServerHostname, assignmentServerPort); monitor->setParent(this); connect(this, &QCoreApplication::aboutToQuit, monitor, &AssignmentClientMonitor::aboutToQuit); } else { - AssignmentClient* client = new AssignmentClient(requestAssignmentType, assignmentPool, + AssignmentClient* client = new AssignmentClient(requestAssignmentType, assignmentPool, listenPort, walletUUID, assignmentServerHostname, assignmentServerPort, monitorPort); client->setParent(this); diff --git a/assignment-client/src/AssignmentClientApp.h b/assignment-client/src/AssignmentClientApp.h index bbc60256a7..c4705e30eb 100644 --- a/assignment-client/src/AssignmentClientApp.h +++ b/assignment-client/src/AssignmentClientApp.h @@ -17,15 +17,15 @@ const QString ASSIGNMENT_TYPE_OVERRIDE_OPTION = "t"; const QString ASSIGNMENT_POOL_OPTION = "pool"; +const QString ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION = "p"; const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "wallet"; const QString CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION = "a"; -const QString CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION = "p"; +const QString CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION = "server-port"; const QString ASSIGNMENT_NUM_FORKS_OPTION = "n"; const QString ASSIGNMENT_MIN_FORKS_OPTION = "min"; const QString ASSIGNMENT_MAX_FORKS_OPTION = "max"; const QString ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION = "monitor-port"; - class AssignmentClientApp : public QCoreApplication { Q_OBJECT public: diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index ddea6cc702..5bb31c8c6f 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -28,7 +28,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen const unsigned int minAssignmentClientForks, const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType, QString assignmentPool, - QUuid walletUUID, QString assignmentServerHostname, + quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort) : _numAssignmentClientForks(numAssignmentClientForks), _minAssignmentClientForks(minAssignmentClientForks), @@ -50,7 +50,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen // create a NodeList so we can receive stats from children DependencyManager::registerInheritance(); auto addressManager = DependencyManager::set(); - auto nodeList = DependencyManager::set(); + auto nodeList = DependencyManager::set(listenPort); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssignmentClientStatus, this, "handleChildStatusPacket"); diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index 8463498d7d..93fc9361ad 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -28,7 +28,7 @@ class AssignmentClientMonitor : public QObject { public: AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks, const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType, - QString assignmentPool, QUuid walletUUID, QString assignmentServerHostname, + QString assignmentPool, quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort); ~AssignmentClientMonitor(); diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index c6ad2c9f09..cacc523ebd 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -16,6 +16,7 @@ #include "audio/AudioMixer.h" #include "avatars/AvatarMixer.h" #include "entities/EntityServer.h" +#include "assets/AssetServer.h" ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) { @@ -33,6 +34,8 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) { return new Agent(packet); case Assignment::EntityServerType: return new EntityServer(packet); + case Assignment::AssetServerType: + return new AssetServer(packet); default: return NULL; } diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp new file mode 100644 index 0000000000..9d60b74508 --- /dev/null +++ b/assignment-client/src/assets/AssetServer.cpp @@ -0,0 +1,167 @@ +// +// AssetServer.cpp +// assignment-client/src/assets +// +// Created by Ryan Huffman on 2015/07/21 +// 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 "AssetServer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NetworkLogging.h" +#include "NodeType.h" +#include "SendAssetTask.h" +#include "UploadAssetTask.h" + +const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; + +AssetServer::AssetServer(NLPacket& packet) : + ThreadedAssignment(packet), + _taskPool(this) +{ + + // Most of the work will be I/O bound, reading from disk and constructing packet objects, + // so the ideal is greater than the number of cores on the system. + static const int TASK_POOL_THREAD_COUNT = 50; + _taskPool.setMaxThreadCount(TASK_POOL_THREAD_COUNT); + + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); + packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); + packetReceiver.registerMessageListener(PacketType::AssetUpload, this, "handleAssetUpload"); +} + +void AssetServer::run() { + ThreadedAssignment::commonInit(ASSET_SERVER_LOGGING_TARGET_NAME, NodeType::AssetServer); + + auto nodeList = DependencyManager::get(); + nodeList->addNodeTypeToInterestSet(NodeType::Agent); + + _resourcesDirectory = QDir(QCoreApplication::applicationDirPath()).filePath("resources/assets"); + if (!_resourcesDirectory.exists()) { + qDebug() << "Creating resources directory"; + _resourcesDirectory.mkpath("."); + } + qDebug() << "Serving files from: " << _resourcesDirectory.path(); + + // Scan for new files + qDebug() << "Looking for new files in asset directory"; + auto files = _resourcesDirectory.entryInfoList(QDir::Files); + QRegExp filenameRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}(\\..+)?$" }; + for (const auto& fileInfo : files) { + auto filename = fileInfo.fileName(); + if (!filenameRegex.exactMatch(filename)) { + qDebug() << "Found file: " << filename; + if (!fileInfo.isReadable()) { + qDebug() << "\tCan't open file for reading: " << filename; + continue; + } + + // Read file + QFile file { fileInfo.absoluteFilePath() }; + file.open(QFile::ReadOnly); + QByteArray data = file.readAll(); + + auto hash = hashData(data); + auto hexHash = hash.toHex(); + + qDebug() << "\tMoving " << filename << " to " << hexHash; + + file.rename(_resourcesDirectory.absoluteFilePath(hexHash) + "." + fileInfo.suffix()); + } + } +} + +void AssetServer::handleAssetGetInfo(QSharedPointer packet, SharedNodePointer senderNode) { + QByteArray assetHash; + MessageID messageID; + uint8_t extensionLength; + + if (packet->getPayloadSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID) + sizeof(extensionLength))) { + qDebug() << "ERROR bad file request"; + return; + } + + packet->readPrimitive(&messageID); + assetHash = packet->readWithoutCopy(SHA256_HASH_LENGTH); + packet->readPrimitive(&extensionLength); + QByteArray extension = packet->read(extensionLength); + + auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply); + + QByteArray hexHash = assetHash.toHex(); + + replyPacket->writePrimitive(messageID); + replyPacket->write(assetHash); + + QString fileName = QString(hexHash) + "." + extension; + QFileInfo fileInfo { _resourcesDirectory.filePath(fileName) }; + + if (fileInfo.exists() && fileInfo.isReadable()) { + qDebug() << "Opening file: " << fileInfo.filePath(); + replyPacket->writePrimitive(AssetServerError::NoError); + replyPacket->writePrimitive(fileInfo.size()); + } else { + qDebug() << "Asset not found: " << QString(hexHash); + replyPacket->writePrimitive(AssetServerError::AssetNotFound); + } + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(replyPacket), *senderNode); +} + +void AssetServer::handleAssetGet(QSharedPointer packet, SharedNodePointer senderNode) { + + auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + sizeof(DataOffset) + sizeof(DataOffset)); + + if (packet->getPayloadSize() < minSize) { + qDebug() << "ERROR bad file request"; + return; + } + + // Queue task + auto task = new SendAssetTask(packet, senderNode, _resourcesDirectory); + _taskPool.start(task); +} + +void AssetServer::handleAssetUpload(QSharedPointer packetList, SharedNodePointer senderNode) { + + if (senderNode->getCanRez()) { + qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); + + auto task = new UploadAssetTask(packetList, senderNode, _resourcesDirectory); + _taskPool.start(task); + } else { + // this is a node the domain told us is not allowed to rez entities + // for now this also means it isn't allowed to add assets + // so return a packet with error that indicates that + + auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError)); + + MessageID messageID; + packetList->readPrimitive(&messageID); + + // write the message ID and a permission denied error + permissionErrorPacket->writePrimitive(messageID); + permissionErrorPacket->writePrimitive(AssetServerError::PermissionDenied); + + // send off the packet + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode); + } +} + diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h new file mode 100644 index 0000000000..1975f746a9 --- /dev/null +++ b/assignment-client/src/assets/AssetServer.h @@ -0,0 +1,45 @@ +// +// AssetServer.h +// assignment-client/src/assets +// +// Created by Ryan Huffman on 2015/07/21 +// 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 +// + +#ifndef hifi_AssetServer_h +#define hifi_AssetServer_h + +#include + +#include +#include + +#include "AssetUtils.h" + +class AssetServer : public ThreadedAssignment { + Q_OBJECT +public: + AssetServer(NLPacket& packet); + +public slots: + void run(); + +private slots: + void handleAssetGetInfo(QSharedPointer packet, SharedNodePointer senderNode); + void handleAssetGet(QSharedPointer packet, SharedNodePointer senderNode); + void handleAssetUpload(QSharedPointer packetList, SharedNodePointer senderNode); + +private: + static void writeError(NLPacketList* packetList, AssetServerError error); + QDir _resourcesDirectory; + QThreadPool _taskPool; +}; + +inline void writeError(NLPacketList* packetList, AssetServerError error) { + packetList->writePrimitive(error); +} + +#endif diff --git a/assignment-client/src/assets/SendAssetTask.cpp b/assignment-client/src/assets/SendAssetTask.cpp new file mode 100644 index 0000000000..f12603d6c7 --- /dev/null +++ b/assignment-client/src/assets/SendAssetTask.cpp @@ -0,0 +1,89 @@ +// +// SendAssetTask.cpp +// assignment-client/src/assets +// +// Created by Ryan Huffman on 2015/08/26 +// 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 "SendAssetTask.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "AssetUtils.h" + +SendAssetTask::SendAssetTask(QSharedPointer packet, const SharedNodePointer& sendToNode, const QDir& resourcesDir) : + QRunnable(), + _packet(packet), + _senderNode(sendToNode), + _resourcesDir(resourcesDir) +{ + +} + +void SendAssetTask::run() { + MessageID messageID; + uint8_t extensionLength; + DataOffset start, end; + + _packet->readPrimitive(&messageID); + QByteArray assetHash = _packet->read(SHA256_HASH_LENGTH); + _packet->readPrimitive(&extensionLength); + QByteArray extension = _packet->read(extensionLength); + + // `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`. + // `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data, + // starting at index 1. + _packet->readPrimitive(&start); + _packet->readPrimitive(&end); + + QString hexHash = assetHash.toHex(); + + qDebug() << "Received a request for the file (" << messageID << "): " << hexHash << " from " << start << " to " << end; + + qDebug() << "Starting task to send asset: " << hexHash << " for messageID " << messageID; + auto replyPacketList = std::unique_ptr(new NLPacketList(PacketType::AssetGetReply, QByteArray(), true, true)); + + replyPacketList->write(assetHash); + + replyPacketList->writePrimitive(messageID); + + if (end <= start) { + writeError(replyPacketList.get(), AssetServerError::InvalidByteRange); + } else { + QString filePath = _resourcesDir.filePath(QString(hexHash) + "." + QString(extension)); + + QFile file { filePath }; + + if (file.open(QIODevice::ReadOnly)) { + if (file.size() < end) { + writeError(replyPacketList.get(), AssetServerError::InvalidByteRange); + qCDebug(networking) << "Bad byte range: " << hexHash << " " << start << ":" << end; + } else { + auto size = end - start; + file.seek(start); + replyPacketList->writePrimitive(AssetServerError::NoError); + replyPacketList->writePrimitive(size); + replyPacketList->write(file.read(size)); + qCDebug(networking) << "Sending asset: " << hexHash; + } + file.close(); + } else { + qCDebug(networking) << "Asset not found: " << filePath << "(" << hexHash << ")"; + writeError(replyPacketList.get(), AssetServerError::AssetNotFound); + } + } + + auto nodeList = DependencyManager::get(); + nodeList->sendPacketList(std::move(replyPacketList), *_senderNode); +} diff --git a/assignment-client/src/assets/SendAssetTask.h b/assignment-client/src/assets/SendAssetTask.h new file mode 100644 index 0000000000..4e1a35b3fe --- /dev/null +++ b/assignment-client/src/assets/SendAssetTask.h @@ -0,0 +1,38 @@ +// +// SendAssetTask.h +// assignment-client/src/assets +// +// Created by Ryan Huffman on 2015/08/26 +// 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 +// + +#ifndef hifi_SendAssetTask_h +#define hifi_SendAssetTask_h + +#include +#include +#include +#include + +#include "AssetUtils.h" +#include "AssetServer.h" +#include "Node.h" + +class NLPacket; + +class SendAssetTask : public QRunnable { +public: + SendAssetTask(QSharedPointer packet, const SharedNodePointer& sendToNode, const QDir& resourcesDir); + + void run(); + +private: + QSharedPointer _packet; + SharedNodePointer _senderNode; + QDir _resourcesDir; +}; + +#endif diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp new file mode 100644 index 0000000000..1acca295b4 --- /dev/null +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -0,0 +1,80 @@ +// +// UploadAssetTask.cpp +// assignment-client/src/assets +// +// Created by Stephen Birarda on 2015-08-28. +// 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 "UploadAssetTask.h" + +#include +#include + +#include +#include +#include + + +UploadAssetTask::UploadAssetTask(QSharedPointer packetList, SharedNodePointer senderNode, + const QDir& resourcesDir) : + _packetList(packetList), + _senderNode(senderNode), + _resourcesDir(resourcesDir) +{ + +} + +void UploadAssetTask::run() { + auto data = _packetList->getMessage(); + + QBuffer buffer { &data }; + buffer.open(QIODevice::ReadOnly); + + MessageID messageID; + buffer.read(reinterpret_cast(&messageID), sizeof(messageID)); + + uint8_t extensionLength; + buffer.read(reinterpret_cast(&extensionLength), sizeof(extensionLength)); + + QByteArray extension = buffer.read(extensionLength); + + uint64_t fileSize; + buffer.read(reinterpret_cast(&fileSize), sizeof(fileSize)); + + qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes and extension" << extension << "from" + << uuidStringWithoutCurlyBraces(_senderNode->getUUID()); + + auto replyPacket = NLPacket::create(PacketType::AssetUploadReply); + replyPacket->writePrimitive(messageID); + + if (fileSize > MAX_UPLOAD_SIZE) { + replyPacket->writePrimitive(AssetServerError::AssetTooLarge); + } else { + QByteArray fileData = buffer.read(fileSize); + + auto hash = hashData(fileData); + auto hexHash = hash.toHex(); + + qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()) + << "is: (" << hexHash << ") "; + + QFile file { _resourcesDir.filePath(QString(hexHash)) + "." + QString(extension) }; + + if (file.exists()) { + qDebug() << "[WARNING] This file already exists: " << hexHash; + } else { + file.open(QIODevice::WriteOnly); + file.write(fileData); + file.close(); + } + replyPacket->writePrimitive(AssetServerError::NoError); + replyPacket->write(hash); + } + + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(replyPacket), *_senderNode); +} diff --git a/assignment-client/src/assets/UploadAssetTask.h b/assignment-client/src/assets/UploadAssetTask.h new file mode 100644 index 0000000000..c310bfc948 --- /dev/null +++ b/assignment-client/src/assets/UploadAssetTask.h @@ -0,0 +1,37 @@ +// +// UploadAssetTask.h +// assignment-client/src/assets +// +// Created by Stephen Birarda on 2015-08-28. +// 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 +// + +#pragma once + +#ifndef hifi_UploadAssetTask_h +#define hifi_UploadAssetTask_h + +#include +#include +#include +#include + +class NLPacketList; +class Node; + +class UploadAssetTask : public QRunnable { +public: + UploadAssetTask(QSharedPointer packetList, QSharedPointer senderNode, const QDir& resourcesDir); + + void run(); + +private: + QSharedPointer _packetList; + QSharedPointer _senderNode; + QDir _resourcesDir; +}; + +#endif // hifi_UploadAssetTask_h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 04cbb0b325..1d8908845f 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -96,14 +96,11 @@ AudioMixer::AudioMixer(NLPacket& packet) : // SOON auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - - QSet nodeAudioPackets { - PacketType::MicrophoneAudioNoEcho, PacketType::MicrophoneAudioWithEcho, - PacketType::InjectAudio, PacketType::SilentAudioFrame, - PacketType::AudioStreamStats - }; - packetReceiver.registerListenerForTypes(nodeAudioPackets, this, "handleNodeAudioPacket"); + packetReceiver.registerListenerForTypes({ PacketType::MicrophoneAudioNoEcho, PacketType::MicrophoneAudioWithEcho, + PacketType::InjectAudio, PacketType::SilentAudioFrame, + PacketType::AudioStreamStats }, + this, "handleNodeAudioPacket"); packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); } @@ -652,9 +649,6 @@ void AudioMixer::run() { auto nodeList = DependencyManager::get(); - // we do not want this event loop to be the handler for UDP datagrams, so disconnect - disconnect(&nodeList->getNodeSocket(), 0, this, 0); - nodeList->addNodeTypeToInterestSet(NodeType::Agent); nodeList->linkedDataCreateCallback = [](Node* node) { @@ -672,7 +666,7 @@ void AudioMixer::run() { connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); domainHandler.requestDomainSettings(); loop.exec(); - + if (domainHandler.getSettingsObject().isEmpty()) { qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment."; setFinished(true); diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 7e822951d8..4fc5a3785f 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -50,7 +50,7 @@ AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() const { } int AudioMixerClientData::parseData(NLPacket& packet) { - PacketType::Value packetType = packet.getType(); + PacketType packetType = packet.getType(); if (packetType == PacketType::AudioStreamStats) { diff --git a/assignment-client/src/audio/AvatarAudioStream.cpp b/assignment-client/src/audio/AvatarAudioStream.cpp index 4c71777d01..da995c999d 100644 --- a/assignment-client/src/audio/AvatarAudioStream.cpp +++ b/assignment-client/src/audio/AvatarAudioStream.cpp @@ -18,7 +18,7 @@ AvatarAudioStream::AvatarAudioStream(bool isStereo, const InboundAudioStream::Se { } -int AvatarAudioStream::parseStreamProperties(PacketType::Value type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { +int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { int readBytes = 0; if (type == PacketType::SilentAudioFrame) { diff --git a/assignment-client/src/audio/AvatarAudioStream.h b/assignment-client/src/audio/AvatarAudioStream.h index 7dc87f7d41..cc2ff1aca7 100644 --- a/assignment-client/src/audio/AvatarAudioStream.h +++ b/assignment-client/src/audio/AvatarAudioStream.h @@ -25,7 +25,7 @@ private: AvatarAudioStream(const AvatarAudioStream&); AvatarAudioStream& operator= (const AvatarAudioStream&); - int parseStreamProperties(PacketType::Value type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); + int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); }; #endif // hifi_AvatarAudioStream_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 1ab637b57f..195bd53a97 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -259,8 +259,8 @@ void AvatarMixer::broadcastAvatarData() { return; } - PacketSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); - PacketSequenceNumber lastSeqFromSender = otherNode->getLastSequenceNumberForPacketType(PacketType::AvatarData); + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); + AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); if (lastSeqToReceiver > lastSeqFromSender) { // Did we somehow get out of order packets from the sender? @@ -289,7 +289,7 @@ void AvatarMixer::broadcastAvatarData() { // set the last sent sequence number for this sender on the receiver nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNode->getLastSequenceNumberForPacketType(PacketType::AvatarData)); + otherNodeData->getLastReceivedSequenceNumber()); // start a new segment in the PacketList for this avatar avatarPacketList.startSegment(); @@ -539,6 +539,7 @@ void AvatarMixer::run() { qDebug() << "Waiting for domain settings from domain-server."; // block until we get the settingsRequestComplete signal + QEventLoop loop; connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 65660b178e..006f58eb44 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -14,6 +14,9 @@ #include "AvatarMixerClientData.h" int AvatarMixerClientData::parseData(NLPacket& packet) { + // pull the sequence number from the data first + packet.readPrimitive(&_lastReceivedSequenceNumber); + // compute the offset to the data payload return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); } @@ -24,13 +27,13 @@ bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() { return oldValue; } -PacketSequenceNumber AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { +uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { // return the matching PacketSequenceNumber, or the default if we don't have it auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); if (nodeMatch != _lastBroadcastSequenceNumbers.end()) { return nodeMatch->second; } else { - return DEFAULT_SEQUENCE_NUMBER; + return 0; } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index dfd61dafbc..3dbe917ee2 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -36,10 +36,12 @@ public: bool checkAndSetHasReceivedFirstPackets(); - PacketSequenceNumber getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; - void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, PacketSequenceNumber sequenceNumber) + uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; + void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } + + uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; } void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; } @@ -77,8 +79,9 @@ public: void loadJSONStats(QJsonObject& jsonObject) const; private: AvatarData _avatar; - - std::unordered_map _lastBroadcastSequenceNumbers; + + uint16_t _lastReceivedSequenceNumber { 0 }; + std::unordered_map _lastBroadcastSequenceNumbers; bool _hasReceivedFirstPackets = false; quint64 _billboardChangeTimestamp = 0; diff --git a/assignment-client/src/entities/EntityNodeData.h b/assignment-client/src/entities/EntityNodeData.h index d779a0b58d..e4008fcb03 100644 --- a/assignment-client/src/entities/EntityNodeData.h +++ b/assignment-client/src/entities/EntityNodeData.h @@ -22,7 +22,7 @@ public: OctreeQueryNode(), _lastDeletedEntitiesSentAt(0) { } - virtual PacketType::Value getMyPacketType() const { return PacketType::EntityData; } + virtual PacketType getMyPacketType() const { return PacketType::EntityData; } quint64 getLastDeletedEntitiesSentAt() const { return _lastDeletedEntitiesSentAt; } void setLastDeletedEntitiesSentAt(quint64 sentAt) { _lastDeletedEntitiesSentAt = sentAt; } diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 1c4eda3cc0..159fc04bf5 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -28,11 +28,11 @@ public: // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode(); virtual char getMyNodeType() const { return NodeType::EntityServer; } - virtual PacketType::Value getMyQueryMessageType() const { return PacketType::EntityQuery; } + virtual PacketType getMyQueryMessageType() const { return PacketType::EntityQuery; } virtual const char* getMyServerName() const { return MODEL_SERVER_NAME; } virtual const char* getMyLoggingServerTargetName() const { return MODEL_SERVER_LOGGING_TARGET_NAME; } virtual const char* getMyDefaultPersistFilename() const { return LOCAL_MODELS_PERSIST_FILE; } - virtual PacketType::Value getMyEditNackType() const { return PacketType::EntityEditNack; } + virtual PacketType getMyEditNackType() const { return PacketType::EntityEditNack; } virtual QString getMyDomainSettingsKey() const { return QString("entity_server_settings"); } // subclass may implement these method diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 06dcee0a41..a023b9a7fd 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -89,7 +89,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet } // Ask our tree subclass if it can handle the incoming packet... - PacketType::Value packetType = packet->getType(); + PacketType packetType = packet->getType(); if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); @@ -128,7 +128,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet } if (debugProcessPacket) { - qDebug() << " numBytesPacketHeader=" << packet->totalHeadersSize(); + qDebug() << " numBytesPacketHeader=" << NLPacket::totalHeaderSize(packetType); qDebug() << " sizeof(sequence)=" << sizeof(sequence); qDebug() << " sizeof(sentAt)=" << sizeof(sentAt); qDebug() << " atByte (in payload)=" << packet->pos(); @@ -150,7 +150,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet if (debugProcessPacket) { qDebug() << " --- inside while loop ---"; qDebug() << " maxSize=" << maxSize; - qDebug("OctreeInboundPacketProcessor::processPacket() %c " + qDebug("OctreeInboundPacketProcessor::processPacket() %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld maxSize=%d", packetType, packet->getPayload(), packet->getPayloadSize(), editData, packet->pos(), maxSize); @@ -188,7 +188,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet } if (debugProcessPacket) { - qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %c " + qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %hhu " "payload=%p payloadLength=%lld editData=%p payloadPosition=%lld", packetType, packet->getPayload(), packet->getPayloadSize(), editData, packet->pos()); } @@ -207,7 +207,7 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer packet } trackInboundPacket(nodeUUID, sequence, transitTime, editsInPacket, processTime, lockWaitTime); } else { - qDebug("unknown packet ignored... packetType=%d", packetType); + qDebug("unknown packet ignored... packetType=%hhu", packetType); } } diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 6395397c51..f342f2e479 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -24,7 +24,7 @@ OctreeQueryNode::OctreeQueryNode() : _viewSent(false), _octreePacket(), _octreePacketWaiting(false), - _lastOctreePayload(new char[MAX_PACKET_SIZE]), + _lastOctreePayload(new char[udt::MAX_PACKET_SIZE]), _lastOctreePacketLength(0), _duplicatePacketCount(0), _firstSuppressedPacket(usecTimestampNow()), diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 5982eeb4bc..0c691a06a2 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -34,7 +34,7 @@ public: virtual ~OctreeQueryNode(); void init(); // called after creation to set up some virtual items - virtual PacketType::Value getMyPacketType() const = 0; + virtual PacketType getMyPacketType() const = 0; void resetOctreePacket(); // resets octree packet to after "V" header @@ -150,7 +150,7 @@ private: quint64 _lastRootTimestamp; - PacketType::Value _myPacketType; + PacketType _myPacketType; bool _isShuttingDown; SentPacketHistory _sentPacketHistory; diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 62517e698b..417df3204a 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -219,7 +219,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes packetSent = true; int packetSizeWithHeader = nodeData->getPacket().getDataSize(); - thisWastedBytes = MAX_PACKET_SIZE - packetSizeWithHeader; + thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader; _totalWastedBytes += thisWastedBytes; _totalBytes += nodeData->getPacket().getDataSize(); _totalPackets++; @@ -251,7 +251,7 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes packetSent = true; int packetSizeWithHeader = nodeData->getPacket().getDataSize(); - int thisWastedBytes = MAX_PACKET_SIZE - packetSizeWithHeader; + int thisWastedBytes = udt::MAX_PACKET_SIZE - packetSizeWithHeader; _totalWastedBytes += thisWastedBytes; _totalBytes += packetSizeWithHeader; _totalPackets++; @@ -598,7 +598,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus _totalBytes += packet->getDataSize(); _totalPackets++; - _totalWastedBytes += MAX_PACKET_SIZE - packet->getDataSize(); + _totalWastedBytes += udt::MAX_PACKET_SIZE - packet->getDataSize(); } } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 419cdabc58..edaf6283f7 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -63,11 +63,11 @@ public: // Subclasses must implement these methods virtual OctreeQueryNode* createOctreeQueryNode() = 0; virtual char getMyNodeType() const = 0; - virtual PacketType::Value getMyQueryMessageType() const = 0; + virtual PacketType getMyQueryMessageType() const = 0; virtual const char* getMyServerName() const = 0; virtual const char* getMyLoggingServerTargetName() const = 0; virtual const char* getMyDefaultPersistFilename() const = 0; - virtual PacketType::Value getMyEditNackType() const = 0; + virtual PacketType getMyEditNackType() const = 0; virtual QString getMyDomainSettingsKey() const { return QString("octree_server_settings"); } // subclass may implement these method diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index bec80d55b7..6dc4899d29 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -73,7 +73,6 @@ endfunction() macro(AUTOSCRIBE_SHADER_LIB) - file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}") foreach(HIFI_LIBRARY ${ARGN}) #if (NOT TARGET ${HIFI_LIBRARY}) diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index aba8d500a5..ccb5ca2484 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -9,8 +9,8 @@ macro(SETUP_HIFI_LIBRARY) - project(${TARGET_NAME}) - + project(${TARGET_NAME}) + # grab the implemenation and header files file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c") list(APPEND ${TARGET_NAME}_SRCS ${LIB_SRCS}) @@ -33,5 +33,8 @@ macro(SETUP_HIFI_LIBRARY) foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES}) target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE}) endforeach() + + # Don't make scribed shaders cumulative + set(AUTOSCRIBE_SHADER_LIB_SRC "") endmacro(SETUP_HIFI_LIBRARY) \ No newline at end of file diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b04ea95a23..e3ab2c51b8 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -166,6 +166,21 @@ } ] }, + { + "name": "asset_server", + "label": "Asset Server", + "assignment-types": [3], + "settings": [ + { + "name": "enabled", + "type": "checkbox", + "label": "Enabled", + "help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)", + "default": false, + "advanced": true + } + ] + }, { "name": "audio_env", "label": "Audio Environment", diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 0b0e0fedd3..ae827f3a4a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -48,7 +48,8 @@ QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID } const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer - << NodeType::AvatarMixer << NodeType::EntityServer; + << NodeType::AvatarMixer << NodeType::EntityServer + << NodeType::AssetServer; void DomainGatekeeper::processConnectRequestPacket(QSharedPointer packet) { if (packet->getPayloadSize() == 0) { @@ -65,6 +66,16 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer pack return; } + static const NodeSet VALID_NODE_TYPES { + NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent + }; + + if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) { + qDebug() << "Received an invalid node type with connect request. Will not allow connection from" + << nodeConnection.senderSockAddr; + return; + } + // check if this connect request matches an assignment in the queue auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 392c09bd8a..2b59ba7510 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -108,6 +108,11 @@ DomainServer::DomainServer(int argc, char* argv[]) : } } +DomainServer::~DomainServer() { + // destroy the LimitedNodeList before the DomainServer QCoreApplication is down + DependencyManager::destroy(); +} + void DomainServer::aboutToQuit() { // clear the log handler so that Qt doesn't call the destructor on LogHandler @@ -257,7 +262,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { auto nodeList = DependencyManager::set(domainServerPort, domainServerDTLSPort); // no matter the local port, save it to shared mem so that local assignment clients can ask what it is - nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, nodeList->getNodeSocket().localPort()); + nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, nodeList->getSocketLocalPort()); // store our local http ports in shared memory quint16 localHttpPort = DOMAIN_SERVER_HTTP_PORT; @@ -280,10 +285,15 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket"); - packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); + + // NodeList won't be available to the settings manager when it is created, so call registerListener here + packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); + + // register the gatekeeper for the packets it needs to receive + packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); @@ -561,8 +571,19 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet #include #include +#include #include "DomainServerSettingsManager.h" @@ -66,6 +67,21 @@ DomainServerSettingsManager::DomainServerSettingsManager() : QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); } +void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer packet) { + Assignment::Type type; + packet->readPrimitive(&type); + + QJsonObject responseObject = responseObjectForType(QString::number(type)); + auto json = QJsonDocument(responseObject).toJson(); + + auto packetList = std::unique_ptr(new NLPacketList(PacketType::DomainSettings, QByteArray(), true, true)); + + packetList->write(json); + + auto nodeList = DependencyManager::get(); + nodeList->sendPacketList(std::move(packetList), packet->getSenderSockAddr()); +} + void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { _configMap.loadMasterAndUserConfig(argumentList); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 21bf099ef0..321f7b7214 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -18,6 +18,8 @@ #include #include +#include + const QString SETTINGS_PATHS_KEY = "paths"; const QString SETTINGS_PATH = "/settings"; @@ -38,6 +40,10 @@ public: QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } + +private slots: + void processSettingsRequestPacket(QSharedPointer packet); + private: QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false); void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant); diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/handControllerGrab.js similarity index 90% rename from examples/controllers/hydra/hydraGrab.js rename to examples/controllers/handControllerGrab.js index bd060ecc14..2ebea75abc 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -11,7 +11,8 @@ // -Script.include("../../libraries/utils.js"); +Script.include("../libraries/utils.js"); + var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var rightTriggerAction = RIGHT_HAND_CLICK; @@ -41,7 +42,7 @@ var INTERSECT_COLOR = { blue: 10 }; -var GRAB_RADIUS = 0.5; +var GRAB_RADIUS = 1.0; var GRAB_COLOR = { red: 250, @@ -135,7 +136,8 @@ controller.prototype.checkForIntersections = function(origin, direction) { var intersection = Entities.findRayIntersection(pickRay, true); if (intersection.intersects && intersection.properties.collisionsWillMove === 1) { - this.distanceToEntity = Vec3.distance(origin, intersection.properties.position); + var handPosition = Controller.getSpatialControlPosition(this.palm); + this.distanceToEntity = Vec3.distance(handPosition, intersection.properties.position); Entities.editEntity(this.pointer, { linePoints: [ ZERO_VEC, @@ -236,19 +238,24 @@ controller.prototype.update = function() { controller.prototype.grabEntity = function() { var handRotation = this.getHandRotation(); var handPosition = this.getHandPosition(); - - var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation; - var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position; - var offset = Vec3.subtract(objectPosition, handPosition); - var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset); this.closeGrabbing = true; + //check if our entity has instructions on how to be grabbed, otherwise, just use default relative position and rotation + var userData = getEntityUserData(this.grabbedEntity); + var relativePosition = ZERO_VEC; + var relativeRotation = Quat.fromPitchYawRollDegrees(0, 0, 0); + if(userData.spatialKey) { + if(userData.spatialKey.relativePosition) { + relativePosition = userData.spatialKey.relativePosition; + } + if(userData.spatialKey.relativeRotation) { + relativeRotation = userData.spatialKey.relativeRotation; + } + } this.actionID = Entities.addAction("hold", this.grabbedEntity, { - relativePosition: offsetPosition, - relativeRotation: offsetRotation, hand: this.hand, - timeScale: 0.05 + timeScale: 0.05, + relativePosition: relativePosition, + relativeRotation: relativeRotation }); } @@ -305,10 +312,10 @@ controller.prototype.onActionEvent = function(action, state) { self.checkForEntityArrival = true; }, 500); var handPosition = Controller.getSpatialControlPosition(this.palm); - var direction = Controller.getSpatialControlNormal(this.tip); + var direction = Vec3.normalize(Controller.getSpatialControlNormal(this.tip)); //move final destination along line a bit, so it doesnt hit avatar hand Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: Vec3.sum(handPosition, Vec3.multiply(2, direction)) + targetPosition: Vec3.sum(handPosition, Vec3.multiply(3, direction)) }); } } @@ -339,8 +346,6 @@ function cleanup() { leftController.cleanup(); } - - Script.scriptEnding.connect(cleanup); Script.update.connect(update) Controller.actionEvent.connect(onActionEvent); \ No newline at end of file diff --git a/examples/entityScripts/boombox.js b/examples/entityScripts/boombox.js new file mode 100644 index 0000000000..d1d18ec615 --- /dev/null +++ b/examples/entityScripts/boombox.js @@ -0,0 +1,126 @@ +// +// boombox.js +// examples/entityScripts +// +// Created by Brad Hefta-Gaub on 9/3/15. +// Copyright 2015 High Fidelity, Inc. +// +// This is an example of a boom box entity script which when assigned to an entity, will detect when the entity is being touched by the avatars hands +// and start to play a boom box song +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + Script.include("../libraries/utils.js"); + + var _this; + var BOOMBOX_USER_DATA_KEY = "boombox"; + var THE_SONG = "http://hifi-public.s3.amazonaws.com/ryan/freaks7.wav"; + + // this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember + // our this object, so we can access it in cases where we're called without a this (like in the case of various global signals) + DetectTouched = function() { + _this = this; + _this.beingTouched = false; + _this.isPlaying = false; + _this.injector = null; + }; + + DetectTouched.prototype = { + + // update() will be called regulary, because we've hooked the update signal in our preload() function. + // we will check the avatars hand positions and if either hand is in our bounding box, we will notice that + update: function() { + // because the update() signal doesn't have a valid this, we need to use our memorized _this to access our entityID + var entityID = _this.entityID; + + var leftHandPosition = MyAvatar.getLeftPalmPosition(); + var rightHandPosition = MyAvatar.getRightPalmPosition(); + var props = Entities.getEntityProperties(entityID); + var entityMinPoint = props.boundingBox.brn; + var entityMaxPoint = props.boundingBox.tfl; + + // this is our assumed boombox data if it's not known + var defaultBoomboxData = { isPlaying: false, avatarID: null }; + + // this handy function getEntityCustomData() is available in utils.js and it will return just the specific section + // of user data we asked for. If it's not available it returns our default data. + var boomboxData = getEntityCustomData(BOOMBOX_USER_DATA_KEY, entityID, defaultBoomboxData); + + // Only allow interaction if no one else is interacting with the boombox or if we are... + if (!boomboxData.isPlaying || boomboxData.avatarID == MyAvatar.sessionUUID) { + + // If we were not previously being touched, and we just got touched, then we do our touching action. + if (pointInExtents(leftHandPosition, entityMinPoint, entityMaxPoint) || pointInExtents(rightHandPosition, entityMinPoint, entityMaxPoint)) { + + if (!_this.beingTouched) { + print("I was just touched..."); + + // remember we're being grabbed so we can detect being released + _this.beingTouched = true; + + // determine what to do based on if we are currently playing + if (!_this.isPlaying) { + // if we are not currently playing, then start playing. + print("Start playing..."); + + _this.isPlaying = true; + + if (_this.injector == null) { + _this.injector = Audio.playSound(_this.song, { + position: props.position, // position of boombox entity + volume: 0.5, + loop: true + }); + } else { + _this.injector.restart(); + } + setEntityCustomData(BOOMBOX_USER_DATA_KEY, entityID, { isPlaying: true, avatarID: MyAvatar.sessionUUID }); + + } else { + // if we are currently playing, then stop playing + print("Stop playing..."); + _this.isPlaying = false; + _this.injector.stop(); + + setEntityCustomData(BOOMBOX_USER_DATA_KEY, entityID, { isPlaying: false, avatarID: null }); + } // endif (!isPlaying) + + } // endif !beingTouched + } else if (_this.beingTouched) { + // if we are not being grabbed, and we previously were, then we were just released, remember that + // and print out a message + _this.beingTouched = false; + print("I'm am no longer being touched..."); + } + + } else { + // end if for -- only interact if no one else is... + print("someone else is playing the boombox..."); + } + }, + + // preload() will be called when the entity has become visible (or known) to the interface + // it gives us a chance to set our local JavaScript object up. In this case it means: + // * remembering our entityID, so we can access it in cases where we're called without an entityID + // * connecting to the update signal so we can check our grabbed state + preload: function(entityID) { + print("preload!"); + this.entityID = entityID; + Script.update.connect(this.update); + this.song = SoundCache.getSound(THE_SONG); + }, + + // unload() will be called when our entity is no longer available. It may be because we were deleted, + // or because we've left the domain or quit the application. In all cases we want to unhook our connection + // to the update signal + unload: function(entityID) { + Script.update.disconnect(this.update); + }, + }; + + // entity scripts always need to return a newly constructed object of our type + return new DetectTouched(); +}) diff --git a/examples/entityScripts/sprayPaintCan.js b/examples/entityScripts/sprayPaintCan.js index 8ec107f779..914e855349 100644 --- a/examples/entityScripts/sprayPaintCan.js +++ b/examples/entityScripts/sprayPaintCan.js @@ -1,16 +1,6 @@ -// -// sprayPaintCan.js -// examples/entityScripts -// -// Created by Eric Levin on 9/3/15 -// Copyright 2015 High Fidelity, Inc. -// -// This is an example of an entity script for painting with a spraypaint can -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - (function() { + Script.include("../libraries/utils.js"); + SPATIAL_USER_DATA_KEY = "spatialKey"; this.userData = {}; var TIP_OFFSET_Z = 0.14; @@ -36,6 +26,8 @@ }); this.getUserData = function() { + + if (this.properties.userData) { this.userData = JSON.parse(this.properties.userData); } @@ -50,8 +42,7 @@ this.update = function(deltaTime) { self.properties = Entities.getEntityProperties(self.entityId); self.getUserData(); - //Only run the logic if this is the client whose avatar is grabbing - if (self.userData.grabKey && self.userData.grabKey.activated === true && self.userData.grabKey.avatarId === MyAvatar.sessionUUID) { + if (self.userData.grabKey && self.userData.grabKey.activated === true) { if (self.activated !== true) { Entities.editEntity(self.paintStream, { animationSettings: startSetting @@ -70,6 +61,8 @@ this.sprayStream = function() { var forwardVec = Quat.getFront(self.properties.rotation); + forwardVec = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, 90, 0), forwardVec); + var upVec = Quat.getUp(self.properties.rotation); var position = Vec3.sum(self.properties.position, Vec3.multiply(forwardVec, TIP_OFFSET_Z)); position = Vec3.sum(position, Vec3.multiply(upVec, TIP_OFFSET_Y)) @@ -92,11 +85,12 @@ var normal = Vec3.multiply(-1, Quat.getFront(intersection.properties.rotation)); this.paint(intersection.intersection, normal); } + + } this.paint = function(position, normal) { if (!this.painting) { - print("position " + JSON.stringify(position)) this.newStroke(position); this.painting = true; @@ -125,6 +119,7 @@ strokeWidths: this.strokeWidths }); + } this.newStroke = function(position) { @@ -157,10 +152,16 @@ this.entityId = entityId; this.properties = Entities.getEntityProperties(self.entityId); this.getUserData(); - print("USER DATA " + JSON.stringify(this.userData)) - if (this.userData.activated) { + if (this.userData.grabKey && this.userData.grabKey.activated) { this.activated = true; } + if(!this.userData.spatialKey) { + var data = { + relativePosition: {x: 0, y: 0, z: 0}, + relativeRotation: Quat.fromPitchYawRollDegrees(0, 0,0) + } + setEntityCustomData(SPATIAL_USER_DATA_KEY, this.entityId, data); + } this.initialize(); } @@ -173,6 +174,7 @@ running: false }); + this.paintStream = Entities.addEntity({ type: "ParticleEffect", animationSettings: animationSettings, diff --git a/examples/shaders/exampleSkyboxUserDataV2.json b/examples/shaders/exampleSkyboxUserDataV2.json new file mode 100644 index 0000000000..2839baea4c --- /dev/null +++ b/examples/shaders/exampleSkyboxUserDataV2.json @@ -0,0 +1,6 @@ +{ + "ProceduralEntity": + { + "shaderUrl": "https://s3.amazonaws.com/Oculus/shadertoys/relentlessSkybox.fs" + } +} \ No newline at end of file diff --git a/examples/shaders/shadertoys/relentlessSkybox.fs b/examples/shaders/shadertoys/relentlessSkybox.fs new file mode 100644 index 0000000000..79e38bf9aa --- /dev/null +++ b/examples/shaders/shadertoys/relentlessSkybox.fs @@ -0,0 +1,144 @@ +// srtuss, 2013 + +// collecting some design ideas for a new game project. +// no raymarching is used. + +// if i could add a custom soundtrack, it'd use this one (essential for desired sensation) +// http://www.youtube.com/watch?v=1uFAu65tZpo + +//#define GREEN_VERSION + +// ** improved camera shaking +// ** cleaned up code +// ** added stuff to the gates + +// ******************************************************************************************* +// Please do NOT use this shader in your own productions/videos/games without my permission! +// If you'd still like to do so, please drop me a mail (stral@aon.at) +// ******************************************************************************************* +float time = iGlobalTime; + +vec2 rotate(vec2 p, float a) { + return vec2(p.x * cos(a) - p.y * sin(a), p.x * sin(a) + p.y * cos(a)); +} + +float box(vec2 p, vec2 b, float r) { + return length(max(abs(p) - b, 0.0)) - r; +} + +// iq's ray-plane-intersection code +vec3 intersect(in vec3 o, in vec3 d, vec3 c, vec3 u, vec3 v) +{ + vec3 q = o - c; + return vec3( + dot(cross(u, v), q), + dot(cross(q, u), d), + dot(cross(v, q), d)) / dot(cross(v, u), d); +} + +// some noise functions for fast developing +float rand11(float p) { + return fract(sin(p * 591.32) * 43758.5357); +} +float rand12(vec2 p) { + return fract(sin(dot(p.xy, vec2(12.9898, 78.233))) * 43758.5357); +} +vec2 rand21(float p) { + return fract(vec2(sin(p * 591.32), cos(p * 391.32))); +} +vec2 rand22(in vec2 p) +{ + return fract(vec2(sin(p.x * 591.32 + p.y * 154.077), cos(p.x * 391.32 + p.y * 49.077))); +} + +float noise11(float p) { + float fl = floor(p); + return mix(rand11(fl), rand11(fl + 1.0), fract(p)); //smoothstep(0.0, 1.0, fract(p))); +} +float fbm11(float p) { + return noise11(p) * 0.5 + noise11(p * 2.0) * 0.25 + noise11(p * 5.0) * 0.125; +} +vec3 noise31(float p) { + return vec3(noise11(p), noise11(p + 18.952), noise11(p - 11.372)) * 2.0 - 1.0; +} + +// something that looks a bit like godrays coming from the surface +float sky(vec3 p) { + float a = atan(p.x, p.z); + float t = time * 0.1; + float v = rand11(floor(a * 4.0 + t)) * 0.5 + rand11(floor(a * 8.0 - t)) * 0.25 + + rand11(floor(a * 16.0 + t)) * 0.125; + return v; +} + +vec3 voronoi(in vec2 x) +{ + vec2 n = floor(x); // grid cell id + vec2 f = fract(x);// grid internal position + vec2 mg;// shortest distance... + vec2 mr;// ..and second shortest distance + float md = 8.0, md2 = 8.0; + for(int j = -1; j <= 1; j ++) + { + for(int i = -1; i <= 1; i ++) + { + vec2 g = vec2(float(i), float(j)); // cell id + vec2 o = rand22(n + g);// offset to edge point + vec2 r = g + o - f; + + float d = max(abs(r.x), abs(r.y));// distance to the edge + + if(d < md) + { md2 = md; md = d; mr = r; mg = g;} + else if(d < md2) + { md2 = d;} + } + } + return vec3(n + mg, md2 - md); +} + + + +vec3 getSkyboxColor() { + vec3 rd = normalize(_normal); + vec3 ro = vec3(0, 0, 1); + float inten = 0.0; + + // background + float sd = dot(rd, vec3(0.0, 1.0, 0.0)); + inten = pow(1.0 - abs(sd), 20.0) + pow(sky(rd), 5.0) * step(0.0, rd.y) * 0.2; + + vec3 its; + float v, g; + + // voronoi floor layers + for(int i = 0; i < 4; i ++) + { + float layer = float(i); + its = intersect(ro, rd, vec3(0.0, -5.0 - layer * 5.0, 0.0), vec3(1.0, 0.0, 0.0), vec3(0.0, 0.0, 1.0)); + if(its.x > 0.0) + { + vec3 vo = voronoi((its.yz) * 0.05 + 8.0 * rand21(float(i))); + v = exp(-100.0 * (vo.z - 0.02)); + + float fx = 0.0; + + // add some special fx to lowest layer + if(i == 3) + { + float crd = 0.0; //fract(time * 0.2) * 50.0 - 25.0; + float fxi = cos(vo.x * 0.2 + time * 1.5);//abs(crd - vo.x); + fx = clamp(smoothstep(0.9, 1.0, fxi), 0.0, 0.9) * 1.0 * rand12(vo.xy); + fx *= exp(-3.0 * vo.z) * 2.0; + } + inten += v * 0.1 + fx; + } + } + + inten *= 0.4 + (sin(time) * 0.5 + 0.5) * 0.6; + + vec3 ct = vec3(0.6, 0.8, 9.0); + // find a color for the computed intensity + vec3 col = pow(vec3(inten), ct); + return col; +} diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index 03b06b0aef..a6a28caa23 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -34,8 +34,12 @@ IceServer::IceServer(int argc, char* argv[]) : qDebug() << "monitoring http endpoint is listening on " << ICE_SERVER_MONITORING_PORT; _serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT); - // call our process datagrams slot when the UDP socket has packets ready - connect(&_serverSocket, &QUdpSocket::readyRead, this, &IceServer::processDatagrams); + // set processPacket as the verified packet callback for the udt::Socket + _serverSocket.setPacketHandler([this](std::unique_ptr packet) { processPacket(std::move(packet)); }); + + // set packetVersionMatch as the verify packet operator for the udt::Socket + using std::placeholders::_1; + _serverSocket.setPacketFilterOperator(std::bind(&IceServer::packetVersionMatch, this, _1)); // setup our timer to clear inactive peers QTimer* inactivePeerTimer = new QTimer(this); @@ -44,67 +48,69 @@ IceServer::IceServer(int argc, char* argv[]) : } -void IceServer::processDatagrams() { - HifiSockAddr sendingSockAddr; - - while (_serverSocket.hasPendingDatagrams()) { - // setup a buffer to read the packet into - int packetSizeWithHeader = _serverSocket.pendingDatagramSize(); - auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); - - _serverSocket.readDatagram(buffer.get(), packetSizeWithHeader, - sendingSockAddr.getAddressPointer(), sendingSockAddr.getPortPointer()); +bool IceServer::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); + PacketVersion headerVersion = NLPacket::versionInHeader(packet); + + if (headerVersion == versionForPacketType(headerType)) { + return true; + } else { + qDebug() << "Packet version mismatch for packet" << headerType + << "(" << nameForPacketType(headerType) << ") from" << packet.getSenderSockAddr(); - // make sure that this packet at least looks like something we can read - if (packetSizeWithHeader >= Packet::localHeaderSize(PacketType::ICEServerHeartbeat)) { + return false; + } +} + +void IceServer::processPacket(std::unique_ptr packet) { + + auto nlPacket = NLPacket::fromBase(std::move(packet)); + + // make sure that this packet at least looks like something we can read + if (nlPacket->getPayloadSize() >= NLPacket::localHeaderSize(PacketType::ICEServerHeartbeat)) { + + if (nlPacket->getType() == PacketType::ICEServerHeartbeat) { + SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket); - auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, sendingSockAddr); + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr()); + } else if (nlPacket->getType() == PacketType::ICEServerQuery) { + QDataStream heartbeatStream(nlPacket.get()); - PacketType::Value packetType = packet->getType(); + // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? + QUuid senderUUID; + heartbeatStream >> senderUUID; - if (packetType == PacketType::ICEServerHeartbeat) { - SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*packet); + // pull the public and private sock addrs for this peer + HifiSockAddr publicSocket, localSocket; + heartbeatStream >> publicSocket >> localSocket; + + // check if this node also included a UUID that they would like to connect to + QUuid connectRequestID; + heartbeatStream >> connectRequestID; + + SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID); + + if (matchingPeer) { - // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now - peer->activateMatchingOrNewSymmetricSocket(sendingSockAddr); - } else if (packetType == PacketType::ICEServerQuery) { - QDataStream heartbeatStream(packet.get()); + qDebug() << "Sending information for peer" << connectRequestID << "to peer" << senderUUID; - // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? - QUuid senderUUID; - heartbeatStream >> senderUUID; + // we have the peer they want to connect to - send them pack the information for that peer + sendPeerInformationPacket(*matchingPeer, &nlPacket->getSenderSockAddr()); - // pull the public and private sock addrs for this peer - HifiSockAddr publicSocket, localSocket; - heartbeatStream >> publicSocket >> localSocket; + // we also need to send them to the active peer they are hoping to connect to + // create a dummy peer object we can pass to sendPeerInformationPacket - // check if this node also included a UUID that they would like to connect to - QUuid connectRequestID; - heartbeatStream >> connectRequestID; - - SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID); - - if (matchingPeer) { - - qDebug() << "Sending information for peer" << connectRequestID << "to peer" << senderUUID; - - // we have the peer they want to connect to - send them pack the information for that peer - sendPeerInformationPacket(*(matchingPeer.data()), &sendingSockAddr); - - // we also need to send them to the active peer they are hoping to connect to - // create a dummy peer object we can pass to sendPeerInformationPacket - - NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket); - sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket()); - } else { - qDebug() << "Peer" << senderUUID << "asked for" << connectRequestID << "but no matching peer found"; - } + NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket); + sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket()); + } else { + qDebug() << "Peer" << senderUUID << "asked for" << connectRequestID << "but no matching peer found"; } } } } -SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(Packet& packet) { +SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) { // pull the UUID, public and private sock addrs for this peer QUuid senderUUID; @@ -137,14 +143,13 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(Packet& packet) { } void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) { - auto peerPacket = Packet::create(PacketType::ICEServerPeerInformation); + auto peerPacket = NLPacket::create(PacketType::ICEServerPeerInformation); // get the byte array for this peer peerPacket->write(peer.toByteArray()); // write the current packet - _serverSocket.writeDatagram(peerPacket->getData(), peerPacket->getDataSize(), - destinationSockAddr->getAddress(), destinationSockAddr->getPort()); + _serverSocket.writePacket(*peerPacket, *destinationSockAddr); } void IceServer::clearInactivePeers() { diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 7820ae2e22..f1c2c06b65 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -19,7 +19,8 @@ #include #include #include -#include +#include +#include typedef QHash NetworkPeerHash; @@ -29,15 +30,16 @@ public: IceServer(int argc, char* argv[]); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); private slots: - void processDatagrams(); void clearInactivePeers(); private: - - SharedNetworkPeer addOrUpdateHeartbeatingPeer(Packet& incomingPacket); + bool packetVersionMatch(const udt::Packet& packet); + void processPacket(std::unique_ptr packet); + + SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket); void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr); QUuid _id; - QUdpSocket _serverSocket; + udt::Socket _serverSocket; NetworkPeerHash _activePeers; HTTPManager _httpManager; }; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index d7ee9228f4..05a937eef8 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -113,7 +113,7 @@ endif() target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) # link required hifi libraries -link_hifi_libraries(shared octree environment gpu model render fbx networking entities avatars +link_hifi_libraries(shared octree environment gpu gpu-networking procedural model render fbx networking entities avatars audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater plugins display-plugins input-plugins) diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 5c4b979ed9..c3829c7be2 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -28,24 +28,24 @@ Item { anchors.fill: parent onClicked: { root.expanded = !root.expanded; } } - + Column { id: generalCol spacing: 4; x: 4; y: 4; - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize text: "Servers: " + root.serverCount } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Avatars: " + root.avatarCount + text: "Avatars: " + root.avatarCount } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Framerate: " + root.framerate + text: "Framerate: " + root.framerate } Text { color: root.fontColor; @@ -60,12 +60,12 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount + text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2) + text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2) } } } @@ -82,30 +82,35 @@ Item { Column { id: pingCol spacing: 4; x: 4; y: 4; - Text { + Text { color: root.fontColor font.pixelSize: root.fontSize - text: "Audio ping: " + root.audioPing + text: "Audio ping: " + root.audioPing } - Text { + Text { color: root.fontColor font.pixelSize: root.fontSize - text: "Avatar ping: " + root.avatarPing + text: "Avatar ping: " + root.avatarPing } - Text { + Text { color: root.fontColor font.pixelSize: root.fontSize - text: "Entities avg ping: " + root.entitiesPing + text: "Entities avg ping: " + root.entitiesPing } - Text { + Text { color: root.fontColor font.pixelSize: root.fontSize - visible: root.expanded; + text: "Asset ping: " + root.assetPing + } + Text { + color: root.fontColor + font.pixelSize: root.fontSize + visible: root.expanded; text: "Voxel max ping: " + 0 } } } - + Rectangle { width: geoCol.width + 8 height: geoCol.height + 8 @@ -117,34 +122,34 @@ Item { Column { id: geoCol spacing: 4; x: 4; y: 4; - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Position: " + root.position.x.toFixed(1) + ", " + - root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1) + text: "Position: " + root.position.x.toFixed(1) + ", " + + root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1) } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Velocity: " + root.velocity.toFixed(1) + text: "Velocity: " + root.velocity.toFixed(1) } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Yaw: " + root.yaw.toFixed(1) + text: "Yaw: " + root.yaw.toFixed(1) } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded; - text: "Avatar Mixer: " + root.avatarMixerKbps + " kbps, " + - root.avatarMixerPps + "pps"; + visible: root.expanded; + text: "Avatar Mixer: " + root.avatarMixerKbps + " kbps, " + + root.avatarMixerPps + "pps"; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded; - text: "Downloads: "; + visible: root.expanded; + text: "Downloads: "; } } } @@ -159,72 +164,72 @@ Item { Column { id: octreeCol spacing: 4; x: 4; y: 4; - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - text: "Triangles: " + root.triangles + - " / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches + text: "Triangles: " + root.triangles + + " / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches } - Text { - color: root.fontColor; - font.pixelSize: root.fontSize - visible: root.expanded; - text: "\tMesh Parts Rendered Opaque: " + root.meshOpaque + - " / Translucent: " + root.meshTranslucent; - } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize visible: root.expanded; - text: "\tOpaque considered: " + root.opaqueConsidered + - " / Out of view: " + root.opaqueOutOfView + " / Too small: " + root.opaqueTooSmall; + text: "\tMesh Parts Rendered Opaque: " + root.meshOpaque + + " / Translucent: " + root.meshTranslucent; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: !root.expanded - text: "Octree Elements Server: " + root.serverElements + - " Local: " + root.localElements; + visible: root.expanded; + text: "\tOpaque considered: " + root.opaqueConsidered + + " / Out of view: " + root.opaqueOutOfView + " / Too small: " + root.opaqueTooSmall; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "Octree Sending Mode: " + root.sendingMode; + visible: !root.expanded + text: "Octree Elements Server: " + root.serverElements + + " Local: " + root.localElements; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "Octree Packets to Process: " + root.packetStats; + visible: root.expanded + text: "Octree Sending Mode: " + root.sendingMode; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "Octree Elements - "; + visible: root.expanded + text: "Octree Packets to Process: " + root.packetStats; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "\tServer: " + root.serverElements + - " Internal: " + root.serverInternal + - " Leaves: " + root.serverLeaves; + visible: root.expanded + text: "Octree Elements - "; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "\tLocal: " + root.localElements + - " Internal: " + root.localInternal + - " Leaves: " + root.localLeaves; + visible: root.expanded + text: "\tServer: " + root.serverElements + + " Internal: " + root.serverInternal + + " Leaves: " + root.serverLeaves; } - Text { + Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "LOD: " + root.lodStatus; + visible: root.expanded + text: "\tLocal: " + root.localElements + + " Internal: " + root.localInternal + + " Leaves: " + root.localLeaves; + } + Text { + color: root.fontColor; + font.pixelSize: root.fontSize + visible: root.expanded + text: "LOD: " + root.lodStatus; } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 113e5dae6c..b8ddf1c4c2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -52,6 +52,7 @@ #include #include +#include #include #include #include @@ -310,6 +311,7 @@ bool setupEssentials(int& argc, char** argv) { auto autoUpdater = DependencyManager::set(); auto pathUtils = DependencyManager::set(); auto actionFactory = DependencyManager::set(); + auto assetClient = DependencyManager::set(); auto userInputMapper = DependencyManager::set(); return true; @@ -456,6 +458,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : audioThread->start(); + QThread* assetThread = new QThread; + + assetThread->setObjectName("Asset Thread"); + auto assetClient = DependencyManager::get(); + + assetClient->moveToThread(assetThread); + + assetThread->start(); + const DomainHandler& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); @@ -485,8 +496,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList.data(), &NodeList::uuidChanged, _myAvatar, &MyAvatar::setSessionUUID); connect(nodeList.data(), &NodeList::uuidChanged, this, &Application::setSessionUUID); connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset); - connect(&nodeList->getPacketReceiver(), &PacketReceiver::packetVersionMismatch, - this, &Application::notifyPacketVersionMismatch); + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &Application::notifyPacketVersionMismatch); // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); @@ -526,7 +536,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // tell the NodeList instance who to tell the domain server we care about nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer - << NodeType::EntityServer); + << NodeType::EntityServer << NodeType::AssetServer); // connect to the packet sent signal of the _entityEditSender connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); @@ -877,6 +887,12 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + + // cleanup the AssetClient thread + QThread* assetThread = DependencyManager::get()->thread(); + DependencyManager::destroy(); + assetThread->quit(); + assetThread->wait(); QThread* nodeThread = DependencyManager::get()->thread(); @@ -886,7 +902,7 @@ Application::~Application() { // ask the node thread to quit and wait until it is done nodeThread->quit(); nodeThread->wait(); - + Leapmotion::destroy(); RealSense::destroy(); ConnexionClient::getInstance().destroy(); @@ -2040,6 +2056,7 @@ void Application::sendPingPackets() { case NodeType::AvatarMixer: case NodeType::AudioMixer: case NodeType::EntityServer: + case NodeType::AssetServer: return true; default: return false; @@ -3028,7 +3045,7 @@ int Application::sendNackPackets() { return packetsSent; } -void Application::queryOctree(NodeType_t serverType, PacketType::Value packetType, NodeToJurisdictionMap& jurisdictions) { +void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) { //qCDebug(interfaceapp) << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView(); bool wantExtraDebugging = getLogger()->extraDebugging(); @@ -3797,6 +3814,9 @@ void Application::nodeAdded(SharedNodePointer node) { if (node->getType() == NodeType::AvatarMixer) { // new avatar mixer, send off our identity packet right away _myAvatar->sendIdentityPacket(); + } else if (node->getType() == NodeType::AssetServer) { + // the addition of an asset-server always re-enables the upload to asset server menu option + Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(true); } } @@ -3844,6 +3864,10 @@ void Application::nodeKilled(SharedNodePointer node) { } else if (node->getType() == NodeType::AvatarMixer) { // our avatar mixer has gone away - clear the hash of avatars DependencyManager::get()->clearOtherAvatars(); + } else if (node->getType() == NodeType::AssetServer + && !DependencyManager::get()->soloNodeOfType(NodeType::AssetServer)) { + // this was our last asset server - disable the menu option to upload an asset + Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(false); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 317832d9f5..10901b4644 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -499,7 +499,7 @@ private: void renderLookatIndicator(glm::vec3 pointOfInterest); - void queryOctree(NodeType_t serverType, PacketType::Value packetType, NodeToJurisdictionMap& jurisdictions); + void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions); void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum); glm::vec3 getSunDirection(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ef499db5e1..d54f69b56d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -22,7 +22,6 @@ #include #include - #include "Application.h" #include "AccountManager.h" #include "audio/AudioScope.h" @@ -33,13 +32,15 @@ #include "devices/3DConnexionClient.h" #include "MainWindow.h" #include "scripting/MenuScriptingInterface.h" -#if defined(Q_OS_MAC) || defined(Q_OS_WIN) -#include "SpeechRecognizer.h" -#endif +#include "ui/AssetUploadDialogFactory.h" #include "ui/DialogsManager.h" #include "ui/StandAloneJSConsole.h" #include "InterfaceLogging.h" +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) +#include "SpeechRecognizer.h" +#endif + #include "Menu.h" Menu* Menu::_instance = NULL; @@ -353,7 +354,21 @@ Menu::Menu() { addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, // QML Qt::SHIFT | Qt::Key_L, dialogsManager.data(), SLOT(lodTools())); - + + MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets"); + + auto& assetDialogFactory = AssetUploadDialogFactory::getInstance(); + assetDialogFactory.setParent(this); + + QAction* assetUpload = addActionToQMenuAndActionHash(assetDeveloperMenu, + MenuOption::UploadAsset, + 0, + &assetDialogFactory, + SLOT(showDialog())); + + // disable the asset upload action by default - it gets enabled only if asset server becomes present + assetUpload->setEnabled(false); + MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index f3bfbce3d1..7d63c64446 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -288,6 +288,7 @@ namespace MenuOption { const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; const QString TurnWithHead = "Turn using Head"; + const QString UploadAsset = "Upload File to Asset Server"; const QString UseAudioForMouth = "Use Audio for Mouth"; const QString UseCamera = "Use Camera"; const QString VelocityFilter = "Velocity Filter"; diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index 1abbb21089..4a92e7e8ac 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -19,12 +19,8 @@ OctreePacketProcessor::OctreePacketProcessor() { auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - QSet types { - PacketType::OctreeStats, PacketType::EntityData, - PacketType::EntityErase, PacketType::OctreeStats - }; - - packetReceiver.registerDirectListenerForTypes(types, this, "handleOctreePacket"); + packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, + this, "handleOctreePacket"); } void OctreePacketProcessor::handleOctreePacket(QSharedPointer packet, SharedNodePointer senderNode) { @@ -44,7 +40,7 @@ void OctreePacketProcessor::processPacket(QSharedPointer packet, Share Application* app = Application::getInstance(); bool wasStatsPacket = false; - PacketType::Value octreePacketType = packet->getType(); + PacketType octreePacketType = packet->getType(); // note: PacketType_OCTREE_STATS can have PacketType_VOXEL_DATA // immediately following them inside the same packet. So, we process the PacketType_OCTREE_STATS first @@ -68,17 +64,17 @@ void OctreePacketProcessor::processPacket(QSharedPointer packet, Share } } // fall through to piggyback message - PacketType::Value packetType = packet->getType(); + PacketType packetType = packet->getType(); // check version of piggyback packet against expected version if (packet->getVersion() != versionForPacketType(packet->getType())) { - static QMultiMap versionDebugSuppressMap; + static QMultiMap versionDebugSuppressMap; const QUuid& senderUUID = packet->getSourceID(); if (!versionDebugSuppressMap.contains(senderUUID, packetType)) { - qDebug() << "Packet version mismatch on" << packetType << "- Sender" - << senderUUID << "sent" << (int) packetType << "but" + qDebug() << "OctreePacketProcessor - piggyback packet version mismatch on" << packetType << "- Sender" + << senderUUID << "sent" << (int) packet->getVersion() << "but" << (int) versionForPacketType(packetType) << "expected."; emit packetVersionMismatch(); diff --git a/interface/src/ui/AddressBarDialog.h b/interface/src/ui/AddressBarDialog.h index e0be8aa3fb..eec3acdbfc 100644 --- a/interface/src/ui/AddressBarDialog.h +++ b/interface/src/ui/AddressBarDialog.h @@ -14,8 +14,7 @@ #include -class AddressBarDialog : public OffscreenQmlDialog -{ +class AddressBarDialog : public OffscreenQmlDialog { Q_OBJECT HIFI_QML_DECL Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged) diff --git a/interface/src/ui/AssetUploadDialogFactory.cpp b/interface/src/ui/AssetUploadDialogFactory.cpp new file mode 100644 index 0000000000..7d87c17b16 --- /dev/null +++ b/interface/src/ui/AssetUploadDialogFactory.cpp @@ -0,0 +1,159 @@ +// +// AssetUploadDialogFactory.cpp +// interface/src/ui +// +// Created by Stephen Birarda on 2015-08-26. +// 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 "AssetUploadDialogFactory.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +AssetUploadDialogFactory& AssetUploadDialogFactory::getInstance() { + static AssetUploadDialogFactory staticInstance; + return staticInstance; +} + +AssetUploadDialogFactory::AssetUploadDialogFactory() { + +} + +static const QString PERMISSION_DENIED_ERROR = "You do not have permission to upload content to this asset-server."; + +void AssetUploadDialogFactory::showDialog() { + auto nodeList = DependencyManager::get(); + + if (nodeList->getThisNodeCanRez()) { + auto filename = QFileDialog::getOpenFileUrl(_dialogParent, "Select a file to upload"); + + if (!filename.isEmpty()) { + qDebug() << "Selected filename for upload to asset-server: " << filename; + + auto assetClient = DependencyManager::get(); + auto upload = assetClient->createUpload(filename.path()); + + if (upload) { + // connect to the finished signal so we know when the AssetUpload is done + QObject::connect(upload, &AssetUpload::finished, this, &AssetUploadDialogFactory::handleUploadFinished); + + // start the upload now + upload->start(); + } else { + // show a QMessageBox to say that there is no local asset server + QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \ + " to a local asset-server.").arg(QFileInfo(filename.toString()).fileName()); + + QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText); + } + } + } else { + // we don't have permission to upload to asset server in this domain - show the permission denied error + showErrorDialog(QString(), PERMISSION_DENIED_ERROR); + } + +} + +void AssetUploadDialogFactory::handleUploadFinished(AssetUpload* upload, const QString& hash) { + if (upload->getResult() == AssetUpload::Success) { + // show message box for successful upload, with copiable text for ATP hash + QDialog* hashCopyDialog = new QDialog(_dialogParent); + + // delete the dialog on close + hashCopyDialog->setAttribute(Qt::WA_DeleteOnClose); + + // set the window title + hashCopyDialog->setWindowTitle(tr("Successful Asset Upload")); + + // setup a layout for the contents of the dialog + QVBoxLayout* boxLayout = new QVBoxLayout; + + // set the label text (this shows above the text box) + QLabel* lineEditLabel = new QLabel; + lineEditLabel->setText(QString("ATP URL for %1").arg(QFileInfo(upload->getFilename()).fileName())); + + // setup the line edit to hold the copiable text + QLineEdit* lineEdit = new QLineEdit; + + QString atpURL = QString("%1:%2.%3").arg(ATP_SCHEME).arg(hash).arg(upload->getExtension()); + + // set the ATP URL as the text value so it's copiable + lineEdit->insert(atpURL); + + // figure out what size this line edit should be using font metrics + QFontMetrics textMetrics { lineEdit->font() }; + + // set the fixed width on the line edit + // pad it by 10 to cover the border and some extra space on the right side (for clicking) + static const int LINE_EDIT_RIGHT_PADDING { 10 }; + + lineEdit->setFixedWidth(textMetrics.width(atpURL) + LINE_EDIT_RIGHT_PADDING ); + + // left align the ATP URL line edit + lineEdit->home(true); + + // add the label and line edit to the dialog + boxLayout->addWidget(lineEditLabel); + boxLayout->addWidget(lineEdit); + + // setup an OK button to close the dialog + QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); + connect(buttonBox, &QDialogButtonBox::accepted, hashCopyDialog, &QDialog::close); + boxLayout->addWidget(buttonBox); + + // set the new layout on the dialog + hashCopyDialog->setLayout(boxLayout); + + // show the new dialog + hashCopyDialog->show(); + } else { + // figure out the right error message for the message box + QString additionalError; + + switch (upload->getResult()) { + case AssetUpload::PermissionDenied: + additionalError = PERMISSION_DENIED_ERROR; + break; + case AssetUpload::TooLarge: + additionalError = "The uploaded content was too large and could not be stored in the asset-server."; + break; + case AssetUpload::ErrorLoadingFile: + additionalError = "The file could not be opened. Please check your permissions and try again."; + break; + default: + // not handled, do not show a message box + return; + } + + // display a message box with the error + showErrorDialog(QFileInfo(upload->getFilename()).fileName(), additionalError); + } + + upload->deleteLater(); +} + +void AssetUploadDialogFactory::showErrorDialog(const QString& filename, const QString& additionalError) { + QString errorMessage; + + if (!filename.isEmpty()) { + errorMessage += QString("Failed to upload %1.\n\n").arg(filename); + } + + errorMessage += additionalError; + + QMessageBox::warning(_dialogParent, "Failed Upload", errorMessage); +} diff --git a/interface/src/ui/AssetUploadDialogFactory.h b/interface/src/ui/AssetUploadDialogFactory.h new file mode 100644 index 0000000000..50980862e6 --- /dev/null +++ b/interface/src/ui/AssetUploadDialogFactory.h @@ -0,0 +1,42 @@ +// +// AssetUploadDialogFactory.h +// interface/src/ui +// +// Created by Stephen Birarda on 2015-08-26. +// 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 +// + +#pragma once + +#ifndef hifi_AssetUploadDialogFactory_h +#define hifi_AssetUploadDialogFactory_h + +#include + +class AssetUpload; + +class AssetUploadDialogFactory : public QObject { + Q_OBJECT +public: + AssetUploadDialogFactory(const AssetUploadDialogFactory& other) = delete; + AssetUploadDialogFactory& operator=(const AssetUploadDialogFactory& rhs) = delete; + + static AssetUploadDialogFactory& getInstance(); + + void setDialogParent(QWidget* dialogParent) { _dialogParent = dialogParent; } +public slots: + void showDialog(); +private slots: + void handleUploadFinished(AssetUpload* upload, const QString& hash); +private: + AssetUploadDialogFactory(); + + void showErrorDialog(const QString& filename, const QString& additionalError); + + QWidget* _dialogParent { nullptr }; +}; + +#endif // hifi_AssetUploadDialogFactory_h diff --git a/interface/src/ui/BandwidthDialog.cpp b/interface/src/ui/BandwidthDialog.cpp index b3bc934006..e086005783 100644 --- a/interface/src/ui/BandwidthDialog.cpp +++ b/interface/src/ui/BandwidthDialog.cpp @@ -89,10 +89,11 @@ BandwidthDialog::BandwidthDialog(QWidget* parent) : _allChannelDisplays[4] = _otherChannelDisplay = new BandwidthChannelDisplay({NodeType::Unassigned}, form, "Other", "Kbps", 1.0, COLOR2); _allChannelDisplays[5] = _totalChannelDisplay = - new BandwidthChannelDisplay({NodeType::DomainServer, NodeType::EntityServer, - NodeType::EnvironmentServer, NodeType::AudioMixer, NodeType::Agent, - NodeType::AvatarMixer, NodeType::Unassigned}, - form, "Total", "Kbps", 1.0, COLOR2); + new BandwidthChannelDisplay({ + NodeType::DomainServer, NodeType::EntityServer, + NodeType::AudioMixer, NodeType::Agent, + NodeType::AvatarMixer, NodeType::Unassigned + }, form, "Total", "Kbps", 1.0, COLOR2); connect(averageUpdateTimer, SIGNAL(timeout()), this, SLOT(updateTimerTimeout())); averageUpdateTimer->start(1000); diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 58d2550752..308cfc9e8c 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -1,6 +1,6 @@ // // DialogsManager.cpp -// +// interface/src/ui // // Created by Clement on 1/18/15. // Copyright 2015 High Fidelity, Inc. diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index 2db700e72a..68d371021a 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -1,6 +1,6 @@ // // DialogsManager.h -// +// interface/src/ui // // Created by Clement on 1/18/15. // Copyright 2015 High Fidelity, Inc. diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 50c820aa07..25ecf45898 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -16,8 +16,7 @@ #include -class LoginDialog : public OffscreenQmlDialog -{ +class LoginDialog : public OffscreenQmlDialog { Q_OBJECT HIFI_QML_DECL diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index d0bddf5c0e..8ddb767537 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -127,8 +127,10 @@ void Stats::updateStats() { if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); + SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer); STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1); STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1); + STAT_UPDATE(assetPing, assetServerNode ? assetServerNode->getPingMs() : -1); //// Now handle entity servers, since there could be more than one, we average their ping times int totalPingOctree = 0; diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index b9ae44449e..e5c273926b 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -40,6 +40,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, audioPing, 0) STATS_PROPERTY(int, avatarPing, 0) STATS_PROPERTY(int, entitiesPing, 0) + STATS_PROPERTY(int, assetPing, 0) STATS_PROPERTY(QVector3D, position, QVector3D(0, 0, 0) ) STATS_PROPERTY(float, velocity, 0) STATS_PROPERTY(float, yaw, 0) @@ -107,6 +108,7 @@ signals: void audioPingChanged(); void avatarPingChanged(); void entitiesPingChanged(); + void assetPingChanged(); void positionChanged(); void velocityChanged(); void yawChanged(); diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 5f0260db6d..866f443005 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -381,8 +381,8 @@ AnimNodeLoader::AnimNodeLoader(const QUrl& url) : _resource(nullptr) { _resource = new Resource(url); - connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(onRequestDone(QNetworkReply&))); - connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(onRequestError(QNetworkReply::NetworkError))); + connect(_resource, &Resource::loaded, this, &AnimNodeLoader::onRequestDone); + connect(_resource, &Resource::failed, this, &AnimNodeLoader::onRequestError); } AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& jsonUrl) { @@ -420,8 +420,8 @@ AnimNode::Pointer AnimNodeLoader::load(const QByteArray& contents, const QUrl& j return loadNode(rootVal.toObject(), jsonUrl); } -void AnimNodeLoader::onRequestDone(QNetworkReply& request) { - auto node = load(request.readAll(), _url); +void AnimNodeLoader::onRequestDone(const QByteArray& data) { + auto node = load(data, _url); if (node) { emit success(node); } else { diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index 71b5552879..713d980f06 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -36,7 +36,7 @@ protected: static AnimNode::Pointer load(const QByteArray& contents, const QUrl& jsonUrl); protected slots: - void onRequestDone(QNetworkReply& request); + void onRequestDone(const QByteArray& data); void onRequestError(QNetworkReply::NetworkError error); protected: diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index e7a4fb50a3..dfe1f2cbed 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -39,14 +39,16 @@ QSharedPointer AnimationCache::createResource(const QUrl& url, const Q return QSharedPointer(new Animation(url), &Resource::allReferencesCleared); } -AnimationReader::AnimationReader(const QUrl& url, QNetworkReply* reply) : +Animation::Animation(const QUrl& url) : Resource(url) {} + +AnimationReader::AnimationReader(const QUrl& url, const QByteArray& data) : _url(url), - _reply(reply) { + _data(data) { } void AnimationReader::run() { try { - if (!_reply) { + if (_data.isEmpty()) { throw QString("Reply is NULL ?!"); } QString urlname = _url.path().toLower(); @@ -58,7 +60,7 @@ void AnimationReader::run() { // Parse the FBX directly from the QNetworkReply FBXGeometry* fbxgeo = nullptr; if (_url.path().toLower().endsWith(".fbx")) { - fbxgeo = readFBX(_reply, QVariantHash(), _url.path()); + fbxgeo = readFBX(_data, QVariantHash(), _url.path()); } else { QString errorStr("usupported format"); emit onError(299, errorStr); @@ -71,11 +73,8 @@ void AnimationReader::run() { } catch (const QString& error) { emit onError(299, error); } - _reply->deleteLater(); } -Animation::Animation(const QUrl& url) : Resource(url) {} - bool Animation::isLoaded() const { return _loaded && _geometry; } @@ -108,9 +107,9 @@ const QVector& Animation::getFramesReference() const { return _geometry->animationFrames; } -void Animation::downloadFinished(QNetworkReply* reply) { +void Animation::downloadFinished(const QByteArray& data) { // parse the animation/fbx file on a background thread. - AnimationReader* animationReader = new AnimationReader(reply->url(), reply); + AnimationReader* animationReader = new AnimationReader(_url, data); connect(animationReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(animationParseSuccess(FBXGeometry*))); connect(animationReader, SIGNAL(onError(int, QString)), SLOT(animationParseError(int, QString))); QThreadPool::globalInstance()->start(animationReader); diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index af07eda9a4..50184862eb 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -65,7 +65,7 @@ public: const QVector& getFramesReference() const; protected: - virtual void downloadFinished(QNetworkReply* reply); + virtual void downloadFinished(const QByteArray& data) override; protected slots: void animationParseSuccess(FBXGeometry* geometry); @@ -81,7 +81,7 @@ class AnimationReader : public QObject, public QRunnable { Q_OBJECT public: - AnimationReader(const QUrl& url, QNetworkReply* reply); + AnimationReader(const QUrl& url, const QByteArray& data); virtual void run(); signals: @@ -90,7 +90,7 @@ signals: private: QUrl _url; - QNetworkReply* _reply; + QByteArray _data; }; class AnimationDetails { diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index d552db1735..3363bb7196 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -164,7 +164,7 @@ int InboundAudioStream::parseData(NLPacket& packet) { return packet.pos(); } -int InboundAudioStream::parseStreamProperties(PacketType::Value type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { +int InboundAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { if (type == PacketType::SilentAudioFrame) { quint16 numSilentSamples = 0; memcpy(&numSilentSamples, packetAfterSeqNum.constData(), sizeof(quint16)); @@ -177,7 +177,7 @@ int InboundAudioStream::parseStreamProperties(PacketType::Value type, const QByt } } -int InboundAudioStream::parseAudioData(PacketType::Value type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { +int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int numAudioSamples) { return _ringBuffer.writeData(packetAfterStreamProperties.data(), numAudioSamples * sizeof(int16_t)); } diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index a0994e42bd..5dfb75272b 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -194,11 +194,11 @@ protected: /// parses the info between the seq num and the audio data in the network packet and calculates /// how many audio samples this packet contains (used when filling in samples for dropped packets). /// default implementation assumes no stream properties and raw audio samples after stream propertiess - virtual int parseStreamProperties(PacketType::Value type, const QByteArray& packetAfterSeqNum, int& networkSamples); + virtual int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& networkSamples); /// parses the audio data in the network packet. /// default implementation assumes packet contains raw audio samples after stream properties - virtual int parseAudioData(PacketType::Value type, const QByteArray& packetAfterStreamProperties, int networkSamples); + virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); /// writes silent samples to the buffer that may be dropped to reduce latency caused by the buffer virtual int writeDroppableSilentSamples(int silentSamples); diff --git a/libraries/audio/src/InjectedAudioStream.cpp b/libraries/audio/src/InjectedAudioStream.cpp index e7633c49e7..54e0f92bea 100644 --- a/libraries/audio/src/InjectedAudioStream.cpp +++ b/libraries/audio/src/InjectedAudioStream.cpp @@ -30,7 +30,7 @@ InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, const bo const uchar MAX_INJECTOR_VOLUME = 255; -int InjectedAudioStream::parseStreamProperties(PacketType::Value type, +int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { // setup a data stream to read from this packet diff --git a/libraries/audio/src/InjectedAudioStream.h b/libraries/audio/src/InjectedAudioStream.h index 2460f83f40..60c36dfb12 100644 --- a/libraries/audio/src/InjectedAudioStream.h +++ b/libraries/audio/src/InjectedAudioStream.h @@ -31,7 +31,7 @@ private: InjectedAudioStream& operator= (const InjectedAudioStream&); AudioStreamStats getAudioStreamStats() const; - int parseStreamProperties(PacketType::Value type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); + int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples); const QUuid _streamIdentifier; float _radius; diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index f596c3a8b3..d236ac7aad 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -42,7 +42,7 @@ int MixedProcessedAudioStream::writeLastFrameRepeatedWithFade(int samples) { return deviceSamplesWritten; } -int MixedProcessedAudioStream::parseAudioData(PacketType::Value type, const QByteArray& packetAfterStreamProperties, int networkSamples) { +int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples) { emit addedStereoSamples(packetAfterStreamProperties); diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index 265095597b..5ea0157421 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -35,7 +35,7 @@ public: protected: int writeDroppableSilentSamples(int silentSamples); int writeLastFrameRepeatedWithFade(int samples); - int parseAudioData(PacketType::Value type, const QByteArray& packetAfterStreamProperties, int networkSamples); + int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties, int networkSamples); private: int networkToDeviceSamples(int networkSamples); diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 7dc6010f8f..2457bda74a 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -56,16 +56,17 @@ Sound::Sound(const QUrl& url, bool isStereo) : } -void Sound::downloadFinished(QNetworkReply* reply) { +void Sound::downloadFinished(const QByteArray& data) { // replace our byte array with the downloaded data - QByteArray rawAudioByteArray = reply->readAll(); - QString fileName = reply->url().fileName(); + QByteArray rawAudioByteArray = QByteArray(data); + QString fileName = getURL().fileName(); const QString WAV_EXTENSION = ".wav"; - if (reply->hasRawHeader("Content-Type") || fileName.endsWith(WAV_EXTENSION)) { + if (fileName.endsWith(WAV_EXTENSION)) { - QByteArray headerContentType = reply->rawHeader("Content-Type"); + QString headerContentType = "audio/x-wav"; + //QByteArray headerContentType = reply->rawHeader("Content-Type"); // WAV audio file encountered if (headerContentType == "audio/x-wav" @@ -80,9 +81,9 @@ void Sound::downloadFinished(QNetworkReply* reply) { } else { // check if this was a stereo raw file // since it's raw the only way for us to know that is if the file was called .stereo.raw - if (reply->url().fileName().toLower().endsWith("stereo.raw")) { + if (fileName.toLower().endsWith("stereo.raw")) { _isStereo = true; - qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << reply->url() << "as stereo audio file."; + qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file."; } // Process as RAW file @@ -94,7 +95,6 @@ void Sound::downloadFinished(QNetworkReply* reply) { } _isReady = true; - reply->deleteLater(); } void Sound::downSample(const QByteArray& rawAudioByteArray) { diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 9aa92feea1..842c395a7d 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -39,7 +39,7 @@ private: void downSample(const QByteArray& rawAudioByteArray); void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); - virtual void downloadFinished(QNetworkReply* reply); + virtual void downloadFinished(const QByteArray& data) override; }; typedef QSharedPointer SharedSoundPointer; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 190214017b..e1bb6ae8a0 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -201,8 +201,7 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { _headData->_isFaceTrackerConnected = true; } - QByteArray avatarDataByteArray; - avatarDataByteArray.resize(MAX_PACKET_SIZE); + QByteArray avatarDataByteArray(udt::MAX_PACKET_SIZE, 0); unsigned char* destinationBuffer = reinterpret_cast(avatarDataByteArray.data()); unsigned char* startPosition = destinationBuffer; @@ -1139,14 +1138,17 @@ void AvatarData::setJointMappingsFromNetworkReply() { void AvatarData::sendAvatarDataPacket() { auto nodeList = DependencyManager::get(); - + // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed. // this is to guard against a joint moving once, the packet getting lost, and the joint never moving again. bool sendFullUpdate = randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO; QByteArray avatarByteArray = toByteArray(true, sendFullUpdate); doneEncoding(true); - auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size()); + static AvatarDataSequenceNumber sequenceNumber = 0; + + auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); + avatarPacket->writePrimitive(sequenceNumber++); avatarPacket->write(avatarByteArray); nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index b7b7352cac..53818cacab 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -59,9 +59,11 @@ typedef unsigned long long quint64; #include "Recorder.h" #include "Referential.h" -typedef std::shared_ptr AvatarSharedPointer; -typedef std::weak_ptr AvatarWeakPointer; -typedef QHash AvatarHash; +using AvatarSharedPointer = std::shared_ptr; +using AvatarWeakPointer = std::weak_ptr; +using AvatarHash = QHash; + +using AvatarDataSequenceNumber = uint16_t; // avatar motion behaviors const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0; diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index c4dddb8971..3787beb32b 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -26,4 +26,4 @@ find_package(PolyVox REQUIRED) target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) -link_hifi_libraries(shared gpu script-engine render render-utils) +link_hifi_libraries(shared gpu gpu-networking procedural script-engine render render-utils) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 72400dcefb..fa8c0eb633 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "EntityTreeRenderer.h" @@ -454,13 +455,24 @@ void EntityTreeRenderer::applyZonePropertiesToScene(std::shared_ptrendOverrideEnvironmentData(); auto stage = scene->getSkyStage(); if (zone->getBackgroundMode() == BACKGROUND_MODE_SKYBOX) { - stage->getSkybox()->setColor(zone->getSkyboxProperties().getColorVec3()); + auto skybox = stage->getSkybox(); + skybox->setColor(zone->getSkyboxProperties().getColorVec3()); + static QString userData; + if (userData != zone->getUserData()) { + userData = zone->getUserData(); + QSharedPointer procedural(new Procedural(userData)); + if (procedural->_enabled) { + skybox->setProcedural(procedural); + } else { + skybox->setProcedural(QSharedPointer()); + } + } if (zone->getSkyboxProperties().getURL().isEmpty()) { - stage->getSkybox()->setCubemap(gpu::TexturePointer()); + skybox->setCubemap(gpu::TexturePointer()); } else { // Update the Texture of the Skybox with the one pointed by this zone auto cubeMap = DependencyManager::get()->getTexture(zone->getSkyboxProperties().getURL(), CUBE_TEXTURE); - stage->getSkybox()->setCubemap(cubeMap->getGPUTexture()); + skybox->setCubemap(cubeMap->getGPUTexture()); } stage->setBackgroundMode(model::SunSkyStage::SKY_BOX); } else { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 25eb87b422..1bfbdaf810 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -43,8 +43,8 @@ public: virtual ~EntityTreeRenderer(); virtual char getMyNodeType() const { return NodeType::EntityServer; } - virtual PacketType::Value getMyQueryMessageType() const { return PacketType::EntityQuery; } - virtual PacketType::Value getExpectedPacketType() const { return PacketType::EntityData; } + virtual PacketType getMyQueryMessageType() const { return PacketType::EntityQuery; } + virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; } virtual void renderElement(OctreeElement* element, RenderArgs* args); virtual float getSizeScale() const; virtual int getBoundaryLevelAdjust() const; diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 738c677104..525226b0e8 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -21,6 +21,8 @@ #include #include "RenderableDebugableEntityItem.h" +#include "../render-utils/simple_vert.h" +#include "../render-utils/simple_frag.h" EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); @@ -42,11 +44,18 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha()); if (!_procedural) { - _procedural.reset(new ProceduralInfo(this)); + _procedural.reset(new Procedural(this->getUserData())); + _procedural->_vertexSource = simple_vert; + _procedural->_fragmentSource = simple_frag; + _procedural->_state->setCullMode(gpu::State::CULL_NONE); + _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); + _procedural->_state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } if (_procedural->ready()) { - _procedural->prepare(batch); + _procedural->prepare(batch, this->getDimensions()); DependencyManager::get()->renderSolidCube(batch, 1.0f, _procedural->getColor(cubeColor)); } else { DependencyManager::get()->renderSolidCube(batch, 1.0f, cubeColor); diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.h b/libraries/entities-renderer/src/RenderableBoxEntityItem.h index e317163683..838022c7d4 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.h @@ -13,10 +13,11 @@ #define hifi_RenderableBoxEntityItem_h #include -#include "RenderableEntityItem.h" -#include "RenderableProceduralItem.h" +#include -class RenderableBoxEntityItem : public BoxEntityItem, RenderableProceduralItem { +#include "RenderableEntityItem.h" + +class RenderableBoxEntityItem : public BoxEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -28,6 +29,8 @@ public: virtual void setUserData(const QString& value); SIMPLE_RENDERABLE() +private: + QSharedPointer _procedural; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index eed908e429..8a05591b4b 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -227,7 +227,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (hasModel()) { if (_model) { - if (getModelURL() != _model->getURLAsString()) { + if (getModelURL() != _model->getURL().toString()) { qDebug() << "Updating model URL: " << getModelURL(); _model->setURL(getModelURL()); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 40648fe26f..68567372e2 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -228,6 +228,14 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { // update vertex buffer auto vertexBuffer = payload.getVertexBuffer(); size_t numBytes = sizeof(Vertex) * _vertices.size(); + + if (numBytes == 0) { + vertexBuffer->resize(0); + auto indexBuffer = payload.getIndexBuffer(); + indexBuffer->resize(0); + return; + } + vertexBuffer->resize(numBytes); gpu::Byte* data = vertexBuffer->editData(); memcpy(data, &(_vertices[0]), numBytes); @@ -293,7 +301,7 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { payload.setPipeline(_untexturedPipeline); } }); - + _scene->enqueuePendingChanges(pendingChanges); } diff --git a/libraries/entities-renderer/src/RenderableProceduralItem.h b/libraries/entities-renderer/src/RenderableProceduralItem.h deleted file mode 100644 index 37e827d304..0000000000 --- a/libraries/entities-renderer/src/RenderableProceduralItem.h +++ /dev/null @@ -1,60 +0,0 @@ -// -// Created by Bradley Austin Davis on 2015/09/05 -// Copyright 2013-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 -// - -#pragma once -#ifndef hifi_RenderableProcedrualItem_h -#define hifi_RenderableProcedrualItem_h - -#include -#include -#include -#include - -#include -#include -#include -#include - -class EntityItem; -class QJsonObject; - -class RenderableProceduralItem { -protected: - // FIXME better encapsulation - // FIXME better mechanism for extending to things rendered using shaders other than simple.slv - struct ProceduralInfo { - ProceduralInfo(EntityItem* entity); - void parse(); - void parse(const QJsonObject&); - bool ready(); - void prepare(gpu::Batch& batch); - glm::vec4 getColor(const glm::vec4& entityColor); - - bool _enabled{ false }; - uint8_t _version{ 1 }; - gpu::PipelinePointer _pipeline; - gpu::ShaderPointer _vertexShader; - gpu::ShaderPointer _fragmentShader; - gpu::ShaderPointer _shader; - QString _shaderSource; - QString _shaderPath; - QUrl _shaderUrl; - quint64 _shaderModified{ 0 }; - bool _pipelineDirty{ true }; - int32_t _timeSlot{ gpu::Shader::INVALID_LOCATION }; - int32_t _scaleSlot{ gpu::Shader::INVALID_LOCATION }; - uint64_t _start{ 0 }; - NetworkShaderPointer _networkShader; - EntityItem* _entity; - QJsonObject _uniforms; - }; - - QSharedPointer _procedural; -}; - -#endif diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 3b58397a82..82257c67fb 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -21,6 +21,8 @@ #include #include "RenderableDebugableEntityItem.h" +#include "../render-utils/simple_vert.h" +#include "../render-utils/simple_frag.h" EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return std::make_shared(entityID, properties); @@ -47,12 +49,19 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { static const int SLICES = 15, STACKS = 15; if (!_procedural) { - _procedural.reset(new ProceduralInfo(this)); + _procedural.reset(new Procedural(getUserData())); + _procedural->_vertexSource = simple_vert; + _procedural->_fragmentSource = simple_frag; + _procedural->_state->setCullMode(gpu::State::CULL_NONE); + _procedural->_state->setDepthTest(true, true, gpu::LESS_EQUAL); + _procedural->_state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha()); if (_procedural->ready()) { - _procedural->prepare(batch); + _procedural->prepare(batch, getDimensions()); DependencyManager::get()->renderSphere(batch, 0.5f, SLICES, STACKS, _procedural->getColor(sphereColor)); } else { DependencyManager::get()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.h b/libraries/entities-renderer/src/RenderableSphereEntityItem.h index 5036354c04..293ae79029 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.h +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.h @@ -13,10 +13,11 @@ #define hifi_RenderableSphereEntityItem_h #include -#include "RenderableEntityItem.h" -#include "RenderableProceduralItem.h" +#include -class RenderableSphereEntityItem : public SphereEntityItem, RenderableProceduralItem { +#include "RenderableEntityItem.h" + +class RenderableSphereEntityItem : public SphereEntityItem { public: static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); @@ -28,6 +29,9 @@ public: virtual void setUserData(const QString& value); SIMPLE_RENDERABLE(); + +private: + QSharedPointer _procedural; }; diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 5905f7924c..48809287a3 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -28,13 +28,13 @@ void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer packet, SharedNodePointer sendingNode); diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index ab2c39324f..36664f5457 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -11,8 +11,10 @@ #include #include + #include #include +#include #include "RegisteredMetaTypes.h" #include "EntityItemID.h" diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index eec872153d..dbcad8a328 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -724,7 +724,7 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object // // TODO: Implement support for script and visible properties. // -bool EntityItemProperties::encodeEntityEditPacket(PacketType::Value command, EntityItemID id, const EntityItemProperties& properties, +bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, QByteArray& buffer) { OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2731ab19d5..cec6b456a7 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -197,7 +197,7 @@ public: void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } - static bool encodeEntityEditPacket(PacketType::Value command, EntityItemID id, const EntityItemProperties& properties, + static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, QByteArray& buffer); static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index b337c05776..a735129a28 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -32,7 +32,7 @@ EntityScriptingInterface::EntityScriptingInterface() : connect(nodeList.data(), &NodeList::canRezChanged, this, &EntityScriptingInterface::canRezChanged); } -void EntityScriptingInterface::queueEntityMessage(PacketType::Value packetType, +void EntityScriptingInterface::queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties) { getEntityPacketSender()->queueEditEntityMessage(packetType, entityID, properties); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ff693e4585..9edf685464 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -171,7 +171,7 @@ private: bool actionWorker(const QUuid& entityID, std::function actor); bool setVoxels(QUuid entityID, std::function actor); bool setPoints(QUuid entityID, std::function actor); - void queueEntityMessage(PacketType::Value packetType, EntityItemID entityID, const EntityItemProperties& properties); + void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties); /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ba0e3f495f..2879468b5a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -63,7 +63,7 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { resetClientEditStats(); } -bool EntityTree::handlesEditPacketType(PacketType::Value packetType) const { +bool EntityTree::handlesEditPacketType(PacketType packetType) const { // we handle these types of "edit" packets switch (packetType) { case PacketType::EntityAdd: diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 4ade0afc6d..e82e715d69 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -62,10 +62,10 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const { return true; } - virtual PacketType::Value expectedDataPacketType() const { return PacketType::EntityData; } + virtual PacketType expectedDataPacketType() const { return PacketType::EntityData; } virtual bool canProcessVersion(PacketVersion thisVersion) const { return thisVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS; } - virtual bool handlesEditPacketType(PacketType::Value packetType) const; + virtual bool handlesEditPacketType(PacketType packetType) const; virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); diff --git a/libraries/entities/src/EntityTreeHeadlessViewer.h b/libraries/entities/src/EntityTreeHeadlessViewer.h index a14fe14a71..2f6483ae19 100644 --- a/libraries/entities/src/EntityTreeHeadlessViewer.h +++ b/libraries/entities/src/EntityTreeHeadlessViewer.h @@ -31,8 +31,8 @@ public: virtual ~EntityTreeHeadlessViewer(); virtual char getMyNodeType() const { return NodeType::EntityServer; } - virtual PacketType::Value getMyQueryMessageType() const { return PacketType::EntityQuery; } - virtual PacketType::Value getExpectedPacketType() const { return PacketType::EntityData; } + virtual PacketType getMyQueryMessageType() const { return PacketType::EntityQuery; } + virtual PacketType getExpectedPacketType() const { return PacketType::EntityData; } void update(); diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 3eff3bdec5..1dfb7a4587 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -194,10 +194,10 @@ void OBJFace::addFrom(const OBJFace* face, int index) { // add using data from f } bool OBJReader::isValidTexture(const QByteArray &filename) { - if (!_url) { + if (_url.isEmpty()) { return false; } - QUrl candidateUrl = _url->resolved(QUrl(filename)); + QUrl candidateUrl = _url.resolved(QUrl(filename)); QNetworkReply *netReply = request(candidateUrl, true); bool isValid = netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200); netReply->deleteLater(); @@ -330,7 +330,7 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi } QByteArray groupName = tokenizer.getDatum(); currentGroup = groupName; - } else if (token == "mtllib" && _url) { + } else if (token == "mtllib" && !_url.isEmpty()) { if (tokenizer.nextToken() != OBJTokenizer::DATUM_TOKEN) { break; } @@ -340,7 +340,7 @@ bool OBJReader::parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mappi } librariesSeen[libraryName] = true; // Throw away any path part of libraryName, and merge against original url. - QUrl libraryUrl = _url->resolved(QUrl(libraryName).fileName()); + QUrl libraryUrl = _url.resolved(QUrl(libraryName).fileName()); #ifdef WANT_DEBUG qCDebug(modelformat) << "OBJ Reader new library:" << libraryName << " at:" << libraryUrl; #endif @@ -415,17 +415,14 @@ done: } -FBXGeometry* OBJReader::readOBJ(const QByteArray& model, const QVariantHash& mapping) { - QBuffer buffer(const_cast(&model)); +FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url) { + + QBuffer buffer { &model }; buffer.open(QIODevice::ReadOnly); - return readOBJ(&buffer, mapping, nullptr); -} - - -FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url) { + FBXGeometry* geometryPtr = new FBXGeometry(); FBXGeometry& geometry = *geometryPtr; - OBJTokenizer tokenizer(device); + OBJTokenizer tokenizer { &buffer }; float scaleGuess = 1.0f; _url = url; @@ -463,8 +460,8 @@ FBXGeometry* OBJReader::readOBJ(QIODevice* device, const QVariantHash& mapping, // Some .obj files use the convention that a group with uv coordinates that doesn't define a material, should use // a texture with the same basename as the .obj file. - if (url) { - QString filename = url->fileName(); + if (!url.isEmpty()) { + QString filename = url.fileName(); int extIndex = filename.lastIndexOf('.'); // by construction, this does not fail QString basename = filename.remove(extIndex + 1, sizeof("obj")); OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME]; diff --git a/libraries/fbx/src/OBJReader.h b/libraries/fbx/src/OBJReader.h index df4c88553e..0e59c5ad8a 100644 --- a/libraries/fbx/src/OBJReader.h +++ b/libraries/fbx/src/OBJReader.h @@ -71,10 +71,10 @@ public: QHash materials; QNetworkReply* request(QUrl& url, bool isTest); - FBXGeometry* readOBJ(const QByteArray& model, const QVariantHash& mapping); - FBXGeometry* readOBJ(QIODevice* device, const QVariantHash& mapping, QUrl* url); + FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, const QUrl& url = QUrl()); + private: - QUrl* _url = nullptr; + QUrl _url; QHash librariesSeen; bool parseOBJGroup(OBJTokenizer& tokenizer, const QVariantHash& mapping, FBXGeometry& geometry, float& scaleGuess); diff --git a/libraries/gpu-networking/CMakeLists.txt b/libraries/gpu-networking/CMakeLists.txt new file mode 100644 index 0000000000..836afac371 --- /dev/null +++ b/libraries/gpu-networking/CMakeLists.txt @@ -0,0 +1,11 @@ +set(TARGET_NAME gpu-networking) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library() + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + +link_hifi_libraries(shared networking gpu) + diff --git a/libraries/gpu-networking/src/gpu-networking/GpuNetworkingLogging.cpp b/libraries/gpu-networking/src/gpu-networking/GpuNetworkingLogging.cpp new file mode 100644 index 0000000000..38da22969b --- /dev/null +++ b/libraries/gpu-networking/src/gpu-networking/GpuNetworkingLogging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis on 2015/08/07 +// Copyright 2013-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 "GpuNetworkingLogging.h" + +Q_LOGGING_CATEGORY(gpunetwork, "hifi.gpu-network") diff --git a/libraries/gpu-networking/src/gpu-networking/GpuNetworkingLogging.h b/libraries/gpu-networking/src/gpu-networking/GpuNetworkingLogging.h new file mode 100644 index 0000000000..7499340a9b --- /dev/null +++ b/libraries/gpu-networking/src/gpu-networking/GpuNetworkingLogging.h @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis on 2015/08/07 +// Copyright 2013-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 + +Q_DECLARE_LOGGING_CATEGORY(gpunetwork) diff --git a/libraries/render-utils/src/ShaderCache.cpp b/libraries/gpu-networking/src/gpu-networking/ShaderCache.cpp similarity index 82% rename from libraries/render-utils/src/ShaderCache.cpp rename to libraries/gpu-networking/src/gpu-networking/ShaderCache.cpp index e4ef8250b9..d0c5d6631f 100644 --- a/libraries/render-utils/src/ShaderCache.cpp +++ b/libraries/gpu-networking/src/gpu-networking/ShaderCache.cpp @@ -8,13 +8,13 @@ #include "ShaderCache.h" NetworkShader::NetworkShader(const QUrl& url, bool delayLoad) - : Resource(url, delayLoad) {}; + : Resource(url, delayLoad) +{ + +} -void NetworkShader::downloadFinished(QNetworkReply* reply) { - if (reply) { - _source = reply->readAll(); - reply->deleteLater(); - } +void NetworkShader::downloadFinished(const QByteArray& data) { + _source = QString::fromUtf8(data); } ShaderCache& ShaderCache::instance() { diff --git a/libraries/render-utils/src/ShaderCache.h b/libraries/gpu-networking/src/gpu-networking/ShaderCache.h similarity index 90% rename from libraries/render-utils/src/ShaderCache.h rename to libraries/gpu-networking/src/gpu-networking/ShaderCache.h index 7698252924..ffe8d437ce 100644 --- a/libraries/render-utils/src/ShaderCache.h +++ b/libraries/gpu-networking/src/gpu-networking/ShaderCache.h @@ -14,9 +14,9 @@ class NetworkShader : public Resource { public: NetworkShader(const QUrl& url, bool delayLoad); - virtual void downloadFinished(QNetworkReply* reply) override; + virtual void downloadFinished(const QByteArray& data) override; - QByteArray _source; + QString _source; }; using NetworkShaderPointer = QSharedPointer; diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/gpu-networking/src/gpu-networking/TextureCache.cpp similarity index 95% rename from libraries/render-utils/src/TextureCache.cpp rename to libraries/gpu-networking/src/gpu-networking/TextureCache.cpp index c4fb4c9989..6063ff6fa4 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/gpu-networking/src/gpu-networking/TextureCache.cpp @@ -1,6 +1,6 @@ // // TextureCache.cpp -// interface/src/renderer +// libraries/gpu-networking/src // // Created by Andrzej Kapolka on 8/6/13. // Copyright 2013 High Fidelity, Inc. @@ -21,13 +21,11 @@ #include #include #include -#include "PathUtils.h" +#include #include - - -#include "RenderUtilsLogging.h" +#include "GpuNetworkingLogging.h" TextureCache::TextureCache() { const qint64 TEXTURE_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; @@ -215,8 +213,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArr class ImageReader : public QRunnable { public: - ImageReader(const QWeakPointer& texture, TextureType type, QNetworkReply* reply, const QUrl& url = QUrl(), - const QByteArray& content = QByteArray()); + ImageReader(const QWeakPointer& texture, TextureType type, const QByteArray& data, const QUrl& url = QUrl()); virtual void run(); @@ -224,27 +221,27 @@ private: QWeakPointer _texture; TextureType _type; - QNetworkReply* _reply; QUrl _url; QByteArray _content; }; -void NetworkTexture::downloadFinished(QNetworkReply* reply) { +void NetworkTexture::downloadFinished(const QByteArray& data) { // send the reader off to the thread pool - QThreadPool::globalInstance()->start(new ImageReader(_self, _type, reply)); + QThreadPool::globalInstance()->start(new ImageReader(_self, _type, data, _url)); } void NetworkTexture::loadContent(const QByteArray& content) { - QThreadPool::globalInstance()->start(new ImageReader(_self, _type, NULL, _url, content)); + QThreadPool::globalInstance()->start(new ImageReader(_self, _type, content, _url)); } -ImageReader::ImageReader(const QWeakPointer& texture, TextureType type, QNetworkReply* reply, - const QUrl& url, const QByteArray& content) : +ImageReader::ImageReader(const QWeakPointer& texture, TextureType type, const QByteArray& data, + const QUrl& url) : _texture(texture), _type(type), - _reply(reply), _url(url), - _content(content) { + _content(data) +{ + } std::once_flag onceListSupportedFormatsflag; @@ -255,7 +252,7 @@ void listSupportedImageFormats() { foreach(const QByteArray& f, supportedFormats) { formats += QString(f) + ","; } - qCDebug(renderutils) << "List of supported Image formats:" << formats; + qCDebug(gpunetwork) << "List of supported Image formats:" << formats; }); } @@ -297,16 +294,8 @@ public: void ImageReader::run() { QSharedPointer texture = _texture.toStrongRef(); if (texture.isNull()) { - if (_reply) { - _reply->deleteLater(); - } return; } - if (_reply) { - _url = _reply->url(); - _content = _reply->readAll(); - _reply->deleteLater(); - } listSupportedImageFormats(); @@ -323,9 +312,9 @@ void ImageReader::run() { if (originalWidth == 0 || originalHeight == 0 || imageFormat == QImage::Format_Invalid) { if (filenameExtension.empty()) { - qCDebug(renderutils) << "QImage failed to create from content, no file extension:" << _url; + qCDebug(gpunetwork) << "QImage failed to create from content, no file extension:" << _url; } else { - qCDebug(renderutils) << "QImage failed to create from content" << _url; + qCDebug(gpunetwork) << "QImage failed to create from content" << _url; } return; } @@ -333,7 +322,7 @@ void ImageReader::run() { int imageArea = image.width() * image.height(); auto ntex = dynamic_cast(&*texture); if (ntex && (ntex->getType() == CUBE_TEXTURE)) { - qCDebug(renderutils) << "Cube map size:" << _url << image.width() << image.height(); + qCDebug(gpunetwork) << "Cube map size:" << _url << image.width() << image.height(); } int opaquePixels = 0; @@ -384,7 +373,7 @@ void ImageReader::run() { } } if (opaquePixels == imageArea) { - qCDebug(renderutils) << "Image with alpha channel is completely opaque:" << _url; + qCDebug(gpunetwork) << "Image with alpha channel is completely opaque:" << _url; image = image.convertToFormat(QImage::Format_RGB888); } @@ -532,7 +521,7 @@ void ImageReader::run() { faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); } else { - qCDebug(renderutils) << "Failed to find a known cube map layout from this image:" << _url; + qCDebug(gpunetwork) << "Failed to find a known cube map layout from this image:" << _url; return; } diff --git a/libraries/gpu-networking/src/gpu-networking/TextureCache.h b/libraries/gpu-networking/src/gpu-networking/TextureCache.h new file mode 100644 index 0000000000..4e104ab783 --- /dev/null +++ b/libraries/gpu-networking/src/gpu-networking/TextureCache.h @@ -0,0 +1,170 @@ +// +// TextureCache.h +// libraries/gpu-networking/src +// +// Created by Andrzej Kapolka on 8/6/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_TextureCache_h +#define hifi_TextureCache_h + +#include + +#include +#include +#include + +#include +#include + +namespace gpu { +class Batch; +} +class NetworkTexture; + +typedef QSharedPointer NetworkTexturePointer; + +enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, EMISSIVE_TEXTURE, SPLAT_TEXTURE, CUBE_TEXTURE }; + +/// Stores cached textures, including render-to-texture targets. +class TextureCache : public ResourceCache, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture + /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and + /// the second, a set of random unit vectors to be used as noise gradients. + const gpu::TexturePointer& getPermutationNormalTexture(); + + /// Returns an opaque white texture (useful for a default). + const gpu::TexturePointer& getWhiteTexture(); + + /// Returns an opaque gray texture (useful for a default). + const gpu::TexturePointer& getGrayTexture(); + + /// Returns the a pale blue texture (useful for a normal map). + const gpu::TexturePointer& getBlueTexture(); + + /// Returns the a black texture (useful for a default). + const gpu::TexturePointer& getBlackTexture(); + + // Returns a map used to compress the normals through a fitting scale algorithm + const gpu::TexturePointer& getNormalFittingTexture(); + + /// Returns a texture version of an image file + static gpu::TexturePointer getImageTexture(const QString& path); + + /// Loads a texture from the specified URL. + NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false, + const QByteArray& content = QByteArray()); + +protected: + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, const void* extra); + +private: + TextureCache(); + virtual ~TextureCache(); + friend class DilatableNetworkTexture; + + gpu::TexturePointer _permutationNormalTexture; + gpu::TexturePointer _whiteTexture; + gpu::TexturePointer _grayTexture; + gpu::TexturePointer _blueTexture; + gpu::TexturePointer _blackTexture; + gpu::TexturePointer _normalFittingTexture; + + QHash > _dilatableNetworkTextures; +}; + +/// A simple object wrapper for an OpenGL texture. +class Texture { +public: + friend class TextureCache; + friend class DilatableNetworkTexture; + Texture(); + ~Texture(); + + const gpu::TexturePointer& getGPUTexture() const { return _gpuTexture; } + +protected: + gpu::TexturePointer _gpuTexture; + +private: +}; + +/// A texture loaded from the network. + +class NetworkTexture : public Resource, public Texture { + Q_OBJECT + +public: + + NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); + + /// Checks whether it "looks like" this texture is translucent + /// (majority of pixels neither fully opaque or fully transparent). + bool isTranslucent() const { return _translucent; } + + /// Returns the lazily-computed average texture color. + const QColor& getAverageColor() const { return _averageColor; } + + int getOriginalWidth() const { return _originalWidth; } + int getOriginalHeight() const { return _originalHeight; } + int getWidth() const { return _width; } + int getHeight() const { return _height; } + TextureType getType() const { return _type; } +protected: + + virtual void downloadFinished(const QByteArray& data) override; + + Q_INVOKABLE void loadContent(const QByteArray& content); + // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... + Q_INVOKABLE void setImage(const QImage& image, void* texture, bool translucent, const QColor& averageColor, int originalWidth, + int originalHeight); + + virtual void imageLoaded(const QImage& image); + + TextureType _type; + +private: + bool _translucent; + QColor _averageColor; + int _originalWidth; + int _originalHeight; + int _width; + int _height; +}; + +/// Caches derived, dilated textures. +class DilatableNetworkTexture : public NetworkTexture { + Q_OBJECT + +public: + + DilatableNetworkTexture(const QUrl& url, const QByteArray& content); + + /// Returns a pointer to a texture with the requested amount of dilation. + QSharedPointer getDilatedTexture(float dilation); + +protected: + + virtual void imageLoaded(const QImage& image); + virtual void reinsert(); + +private: + + QImage _image; + int _innerRadius; + int _outerRadius; + + QMap > _dilatedTextures; +}; + +#endif // hifi_TextureCache_h diff --git a/libraries/gpu/CMakeLists.txt b/libraries/gpu/CMakeLists.txt index 7a88580f7f..38fe5cb22f 100644 --- a/libraries/gpu/CMakeLists.txt +++ b/libraries/gpu/CMakeLists.txt @@ -7,6 +7,10 @@ setup_hifi_library() link_hifi_libraries(shared) +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + add_dependency_external_projects(glew) find_package(GLEW REQUIRED) add_definitions(-DGLEW_STATIC) diff --git a/libraries/model/CMakeLists.txt b/libraries/model/CMakeLists.txt index 701c132e61..8acb4b0a71 100755 --- a/libraries/model/CMakeLists.txt +++ b/libraries/model/CMakeLists.txt @@ -9,4 +9,4 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared gpu octree) +link_hifi_libraries(shared networking gpu gpu-networking procedural octree) diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 314492881f..c17bf1df72 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -10,10 +10,12 @@ // #include "Skybox.h" -#include "gpu/Batch.h" -#include "gpu/Context.h" -#include "ViewFrustum.h" +#include +#include +#include +#include + #include "Skybox_vert.h" #include "Skybox_frag.h" @@ -38,19 +40,54 @@ void Skybox::setColor(const Color& color) { _color = color; } +void Skybox::setProcedural(QSharedPointer procedural) { + _procedural = procedural; + if (_procedural) { + _procedural->_vertexSource = Skybox_vert; + _procedural->_fragmentSource = Skybox_frag; + // No pipeline state customization + } +} + void Skybox::setCubemap(const gpu::TexturePointer& cubemap) { _cubemap = cubemap; } + void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Skybox& skybox) { + static gpu::BufferPointer theBuffer; + static gpu::Stream::FormatPointer theFormat; - if (skybox.getCubemap()) { - if (skybox.getCubemap()->isDefined()) { + if (skybox._procedural || skybox.getCubemap()) { + if (!theBuffer) { + const float CLIP = 1.0f; + const glm::vec2 vertices[4] = { { -CLIP, -CLIP }, { CLIP, -CLIP }, { -CLIP, CLIP }, { CLIP, CLIP } }; + theBuffer = std::make_shared(sizeof(vertices), (const gpu::Byte*) vertices); + theFormat = std::make_shared(); + theFormat->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ)); + } - static gpu::PipelinePointer thePipeline; - static gpu::BufferPointer theBuffer; - static gpu::Stream::FormatPointer theFormat; + glm::mat4 projMat; + viewFrustum.evalProjectionMatrix(projMat); + + Transform viewTransform; + viewFrustum.evalViewTransform(viewTransform); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewTransform); + batch.setModelTransform(Transform()); // only for Mac + batch.setInputBuffer(gpu::Stream::POSITION, theBuffer, 0, 8); + batch.setInputFormat(theFormat); + + if (skybox._procedural && skybox._procedural->_enabled && skybox._procedural->ready()) { + if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { + batch.setResourceTexture(0, skybox.getCubemap()); + } + + skybox._procedural->prepare(batch, glm::vec3(1)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + } else if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { static gpu::BufferPointer theConstants; + static gpu::PipelinePointer thePipeline; static int SKYBOX_CONSTANTS_SLOT = 0; // need to be defined by the compilation of the shader if (!thePipeline) { auto skyVS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(Skybox_vert))); @@ -72,23 +109,10 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky thePipeline = gpu::PipelinePointer(gpu::Pipeline::create(skyShader, skyState)); - const float CLIP = 1.0f; - const glm::vec2 vertices[4] = { {-CLIP, -CLIP}, {CLIP, -CLIP}, {-CLIP, CLIP}, {CLIP, CLIP}}; - theBuffer = std::make_shared(sizeof(vertices), (const gpu::Byte*) vertices); - - theFormat = std::make_shared(); - theFormat->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ)); - auto color = glm::vec4(1.0f); theConstants = std::make_shared(sizeof(color), (const gpu::Byte*) &color); } - glm::mat4 projMat; - viewFrustum.evalProjectionMatrix(projMat); - - Transform viewTransform; - viewFrustum.evalViewTransform(viewTransform); - if (glm::all(glm::equal(skybox.getColor(), glm::vec3(0.0f)))) { auto color = glm::vec4(1.0f); theConstants->setSubData(0, sizeof(color), (const gpu::Byte*) &color); @@ -96,13 +120,8 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky theConstants->setSubData(0, sizeof(Color), (const gpu::Byte*) &skybox.getColor()); } - batch.setProjectionTransform(projMat); - batch.setViewTransform(viewTransform); - batch.setModelTransform(Transform()); // only for Mac batch.setPipeline(thePipeline); - batch.setInputBuffer(gpu::Stream::POSITION, theBuffer, 0, 8); batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, theConstants, 0, theConstants->getSize()); - batch.setInputFormat(theFormat); batch.setResourceTexture(0, skybox.getCubemap()); batch.draw(gpu::TRIANGLE_STRIP, 4); } diff --git a/libraries/model/src/model/Skybox.h b/libraries/model/src/model/Skybox.h index 7a4550d75a..809ec7e3b0 100755 --- a/libraries/model/src/model/Skybox.h +++ b/libraries/model/src/model/Skybox.h @@ -11,12 +11,13 @@ #ifndef hifi_model_Skybox_h #define hifi_model_Skybox_h -#include "gpu/Texture.h" +#include +#include #include "Light.h" class ViewFrustum; -//class Transform; +struct Procedural; namespace gpu { class Batch; } namespace model { @@ -35,11 +36,13 @@ public: void setCubemap(const gpu::TexturePointer& cubemap); const gpu::TexturePointer& getCubemap() const { return _cubemap; } + void setProcedural(QSharedPointer procedural); + static void render(gpu::Batch& batch, const ViewFrustum& frustum, const Skybox& skybox); protected: gpu::TexturePointer _cubemap; - + QSharedPointer _procedural; Color _color{1.0f, 1.0f, 1.0f}; }; typedef std::shared_ptr< Skybox > SkyboxPointer; diff --git a/libraries/model/src/model/Skybox.slf b/libraries/model/src/model/Skybox.slf index 47444b21bd..382801f52d 100755 --- a/libraries/model/src/model/Skybox.slf +++ b/libraries/model/src/model/Skybox.slf @@ -22,13 +22,29 @@ uniform skyboxBuffer { }; in vec3 _normal; - out vec4 _fragColor; +//PROCEDURAL_COMMON_BLOCK + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2033 void main(void) { + +#ifdef PROCEDURAL + + vec3 color = getSkyboxColor(); + _fragColor = vec4(color, 0.0); + +#else + vec3 coord = normalize(_normal); vec3 texel = texture(cubeMap, coord).rgb; vec3 color = texel * _skybox._color.rgb; vec3 pixel = pow(color, vec3(1.0/2.2)); // manual Gamma correction _fragColor = vec4(pixel, 0.0); + +#endif + } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 3f3234a307..f99e498ebf 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -20,9 +20,10 @@ #include #include +#include "AddressManager.h" #include "NodeList.h" #include "NetworkLogging.h" -#include "AddressManager.h" + const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp new file mode 100644 index 0000000000..959c1562f2 --- /dev/null +++ b/libraries/networking/src/AssetClient.cpp @@ -0,0 +1,248 @@ +// +// AssetClient.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/21 +// 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 "AssetClient.h" + +#include +#include + +#include + +#include "AssetRequest.h" +#include "AssetUpload.h" +#include "NodeList.h" +#include "PacketReceiver.h" +#include "AssetUtils.h" + +MessageID AssetClient::_currentID = 0; + + +AssetClient::AssetClient() { + + setCustomDeleter([](Dependency* dependency){ + static_cast(dependency)->deleteLater(); + }); + + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply"); + packetReceiver.registerMessageListener(PacketType::AssetGetReply, this, "handleAssetGetReply"); + packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply"); +} + +AssetRequest* AssetClient::createRequest(const QString& hash, const QString& extension) { + if (QThread::currentThread() != thread()) { + AssetRequest* req; + QMetaObject::invokeMethod(this, "createRequest", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AssetRequest*, req), + Q_ARG(QString, hash), + Q_ARG(QString, extension)); + return req; + } + + if (hash.length() != SHA256_HASH_HEX_LENGTH) { + qDebug() << "Invalid hash size"; + return nullptr; + } + + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + if (!assetServer) { + qDebug().nospace() << "Could not request " << hash << "." << extension << " since you are not currently connected to an asset-server."; + return nullptr; + } + + return new AssetRequest(this, hash, extension); +} + +AssetUpload* AssetClient::createUpload(const QString& filename) { + if (QThread::currentThread() != thread()) { + AssetUpload* upload; + QMetaObject::invokeMethod(this, "createUpload", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AssetUpload*, upload), + Q_ARG(QString, filename)); + return upload; + } + + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + if (!assetServer) { + qDebug() << "Could not upload" << filename << "since you are not currently connected to an asset-server."; + return nullptr; + } + + return new AssetUpload(this, filename); +} + +bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end, + ReceivedAssetCallback callback) { + if (hash.length() != SHA256_HASH_HEX_LENGTH) { + qDebug() << "Invalid hash size"; + return false; + } + + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + if (assetServer) { + auto packet = NLPacket::create(PacketType::AssetGet); + + auto messageID = ++_currentID; + + qDebug() << "Requesting data from" << start << "to" << end << "of" << hash << "from asset-server."; + + packet->writePrimitive(messageID); + + packet->write(QByteArray::fromHex(hash.toLatin1())); + + packet->writePrimitive(uint8_t(extension.length())); + packet->write(extension.toLatin1()); + + packet->writePrimitive(start); + packet->writePrimitive(end); + + nodeList->sendPacket(std::move(packet), *assetServer); + + _pendingRequests[messageID] = callback; + + return true; + } + + return false; +} + +bool AssetClient::getAssetInfo(const QString& hash, const QString& extension, GetInfoCallback callback) { + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + if (assetServer) { + auto packet = NLPacket::create(PacketType::AssetGetInfo); + + auto messageID = ++_currentID; + packet->writePrimitive(messageID); + packet->write(QByteArray::fromHex(hash.toLatin1())); + packet->writePrimitive(uint8_t(extension.length())); + packet->write(extension.toLatin1()); + + nodeList->sendPacket(std::move(packet), *assetServer); + + _pendingInfoRequests[messageID] = callback; + + return true; + } + + return false; +} + +void AssetClient::handleAssetGetInfoReply(QSharedPointer packet, SharedNodePointer senderNode) { + MessageID messageID; + packet->readPrimitive(&messageID); + auto assetHash = packet->read(SHA256_HASH_LENGTH); + + AssetServerError error; + packet->readPrimitive(&error); + + AssetInfo info { assetHash.toHex(), 0 }; + + if (error == AssetServerError::NoError) { + packet->readPrimitive(&info.size); + } + + if (_pendingInfoRequests.contains(messageID)) { + auto callback = _pendingInfoRequests.take(messageID); + callback(error, info); + } +} + +void AssetClient::handleAssetGetReply(QSharedPointer packetList, SharedNodePointer senderNode) { + QByteArray data = packetList->getMessage(); + QBuffer packet { &data }; + packet.open(QIODevice::ReadOnly); + + auto assetHash = packet.read(SHA256_HASH_LENGTH); + qDebug() << "Got reply for asset: " << assetHash.toHex(); + + MessageID messageID; + packet.read(reinterpret_cast(&messageID), sizeof(messageID)); + + AssetServerError error; + packet.read(reinterpret_cast(&error), sizeof(AssetServerError)); + QByteArray assetData; + + if (!error) { + DataOffset length; + packet.read(reinterpret_cast(&length), sizeof(DataOffset)); + data = packet.read(length); + } else { + qDebug() << "Failure getting asset: " << error; + } + + if (_pendingRequests.contains(messageID)) { + auto callback = _pendingRequests.take(messageID); + callback(error, data); + } +} + +bool AssetClient::uploadAsset(const QByteArray& data, const QString& extension, UploadResultCallback callback) { + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + if (assetServer) { + auto packetList = std::unique_ptr(new NLPacketList(PacketType::AssetUpload, QByteArray(), true, true)); + + auto messageID = ++_currentID; + packetList->writePrimitive(messageID); + + packetList->writePrimitive(static_cast(extension.length())); + packetList->write(extension.toLatin1().constData(), extension.length()); + + qDebug() << "Extension length: " << extension.length(); + qDebug() << "Extension: " << extension; + + uint64_t size = data.length(); + packetList->writePrimitive(size); + packetList->write(data.constData(), size); + + nodeList->sendPacketList(std::move(packetList), *assetServer); + + _pendingUploads[messageID] = callback; + + return true; + } + return false; +} + +void AssetClient::handleAssetUploadReply(QSharedPointer packet, SharedNodePointer senderNode) { + MessageID messageID; + packet->readPrimitive(&messageID); + + AssetServerError error; + packet->readPrimitive(&error); + + QString hashString { "" }; + + if (error) { + qDebug() << "Error uploading file to asset server"; + } else { + auto hash = packet->read(SHA256_HASH_LENGTH); + hashString = hash.toHex(); + + qDebug() << "Successfully uploaded asset to asset-server - SHA256 hash is " << hashString; + } + + if (_pendingUploads.contains(messageID)) { + auto callback = _pendingUploads.take(messageID); + callback(error, hashString); + } +} diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h new file mode 100644 index 0000000000..7fa1771873 --- /dev/null +++ b/libraries/networking/src/AssetClient.h @@ -0,0 +1,63 @@ +// +// AssetClient.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/21 +// 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 +// + + +#ifndef hifi_AssetClient_h +#define hifi_AssetClient_h + +#include + +#include + +#include "AssetUtils.h" +#include "LimitedNodeList.h" +#include "NLPacket.h" + +class AssetRequest; +class AssetUpload; + +struct AssetInfo { + QString hash; + int64_t size; +}; + +using ReceivedAssetCallback = std::function; +using GetInfoCallback = std::function; +using UploadResultCallback = std::function; + +class AssetClient : public QObject, public Dependency { + Q_OBJECT +public: + AssetClient(); + + Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension); + Q_INVOKABLE AssetUpload* createUpload(const QString& filename); + +private slots: + void handleAssetGetInfoReply(QSharedPointer packet, SharedNodePointer senderNode); + void handleAssetGetReply(QSharedPointer packetList, SharedNodePointer senderNode); + void handleAssetUploadReply(QSharedPointer packet, SharedNodePointer senderNode); + +private: + bool getAssetInfo(const QString& hash, const QString& extension, GetInfoCallback callback); + bool getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end, ReceivedAssetCallback callback); + bool uploadAsset(const QByteArray& data, const QString& extension, UploadResultCallback callback); + + static MessageID _currentID; + QHash _pendingRequests; + QHash _pendingInfoRequests; + QHash _pendingUploads; + + friend class AssetRequest; + friend class AssetUpload; +}; + +#endif diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp new file mode 100644 index 0000000000..f989f10ae8 --- /dev/null +++ b/libraries/networking/src/AssetRequest.cpp @@ -0,0 +1,80 @@ +// +// AssetRequest.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/24 +// 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 "AssetRequest.h" + +#include + +#include + +#include "AssetClient.h" +#include "NetworkLogging.h" +#include "NodeList.h" + +AssetRequest::AssetRequest(QObject* parent, const QString& hash, const QString& extension) : + QObject(parent), + _hash(hash), + _extension(extension) +{ + +} + +void AssetRequest::start() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "start", Qt::AutoConnection); + return; + } + + if (_state != NOT_STARTED) { + qCWarning(networking) << "AssetRequest already started."; + return; + } + + _state = WAITING_FOR_INFO; + + auto assetClient = DependencyManager::get(); + assetClient->getAssetInfo(_hash, _extension, [this](AssetServerError error, AssetInfo info) { + _info = info; + _error = error; + + if (_error != NoError) { + qCDebug(networking) << "Got error retrieving asset info for" << _hash; + _state = FINISHED; + emit finished(this); + return; + } + + _state = WAITING_FOR_DATA; + _data.resize(info.size); + + qCDebug(networking) << "Got size of " << _hash << " : " << info.size << " bytes"; + + int start = 0, end = _info.size; + + auto assetClient = DependencyManager::get(); + assetClient->getAsset(_hash, _extension, start, end, [this, start, end](AssetServerError error, + const QByteArray& data) { + Q_ASSERT(data.size() == (end - start)); + + _error = error; + if (_error == NoError) { + memcpy(_data.data() + start, data.constData(), data.size()); + _totalReceived += data.size(); + emit progress(_totalReceived, _info.size); + } else { + qCDebug(networking) << "Got error retrieving asset" << _hash; + } + + _state = FINISHED; + emit finished(this); + }); + }); +} diff --git a/libraries/networking/src/AssetRequest.h b/libraries/networking/src/AssetRequest.h new file mode 100644 index 0000000000..b33954d34e --- /dev/null +++ b/libraries/networking/src/AssetRequest.h @@ -0,0 +1,56 @@ +// +// AssetRequest.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/24 +// 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 +// + +#ifndef hifi_AssetRequest_h +#define hifi_AssetRequest_h + +#include +#include +#include + +#include "AssetClient.h" + +#include "AssetUtils.h" + +class AssetRequest : public QObject { + Q_OBJECT +public: + enum State { + NOT_STARTED = 0, + WAITING_FOR_INFO, + WAITING_FOR_DATA, + FINISHED + }; + + AssetRequest(QObject* parent, const QString& hash, const QString& extension); + + Q_INVOKABLE void start(); + + const QByteArray& getData() const { return _data; } + State getState() const { return _state; } + AssetServerError getError() const { return _error; } + +signals: + void finished(AssetRequest* thisRequest); + void progress(qint64 totalReceived, qint64 total); + +private: + State _state = NOT_STARTED; + AssetServerError _error; + AssetInfo _info; + uint64_t _totalReceived { 0 }; + QString _hash; + QString _extension; + QByteArray _data; + int _numPendingRequests { 0 }; +}; + +#endif diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp new file mode 100644 index 0000000000..b9adba99b7 --- /dev/null +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -0,0 +1,72 @@ +// +// AssetResourceRequest.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 "AssetResourceRequest.h" + +#include "AssetClient.h" +#include "AssetRequest.h" +#include "AssetUtils.h" + +void AssetResourceRequest::doSend() { + // Make request to atp + auto assetClient = DependencyManager::get(); + auto parts = _url.path().split(".", QString::SkipEmptyParts); + auto hash = parts[0]; + auto extension = parts.length() > 1 ? parts[1] : ""; + + if (hash.length() != SHA256_HASH_HEX_LENGTH) { + _result = InvalidURL; + _state = Finished; + + emit finished(); + + return; + } + + auto request = assetClient->createRequest(hash, extension); + + if (!request) { + _result = ServerUnavailable; + _state = Finished; + + emit finished(); + + return; + } + + connect(request, &AssetRequest::progress, this, &AssetResourceRequest::progress); + QObject::connect(request, &AssetRequest::finished, [this](AssetRequest* req) mutable { + Q_ASSERT(_state == InProgress); + Q_ASSERT(req->getState() == AssetRequest::FINISHED); + + switch (req->getError()) { + case NoError: + _data = req->getData(); + _result = Success; + break; + case AssetNotFound: + _result = NotFound; + break; + default: + _result = Error; + break; + } + + _state = Finished; + emit finished(); + }); + + request->start(); +} + +void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + emit progress(bytesReceived, bytesTotal); +} diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h new file mode 100644 index 0000000000..fb9c25e092 --- /dev/null +++ b/libraries/networking/src/AssetResourceRequest.h @@ -0,0 +1,31 @@ +// +// AssetResourceRequest.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 +// + +#ifndef hifi_AssetResourceRequest_h +#define hifi_AssetResourceRequest_h + +#include + +#include "ResourceRequest.h" + +class AssetResourceRequest : public ResourceRequest { + Q_OBJECT +public: + AssetResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + +protected: + virtual void doSend() override; + +private slots: + void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); +}; + +#endif diff --git a/libraries/networking/src/AssetUpload.cpp b/libraries/networking/src/AssetUpload.cpp new file mode 100644 index 0000000000..14e5354f0b --- /dev/null +++ b/libraries/networking/src/AssetUpload.cpp @@ -0,0 +1,68 @@ +// +// AssetUpload.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2015-08-26. +// 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 "AssetUpload.h" + +#include +#include + +#include "AssetClient.h" + +AssetUpload::AssetUpload(QObject* object, const QString& filename) : + _filename(filename) +{ + +} + +void AssetUpload::start() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "start", Qt::AutoConnection); + return; + } + + // try to open the file at the given filename + QFile file { _filename }; + + if (file.open(QIODevice::ReadOnly)) { + + // file opened, read the data and grab the extension + _extension = QFileInfo(_filename).suffix(); + + auto data = file.readAll(); + + // ask the AssetClient to upload the asset and emit the proper signals from the passed callback + auto assetClient = DependencyManager::get(); + + assetClient->uploadAsset(data, _extension, [this](AssetServerError error, const QString& hash){ + switch (error) { + case AssetServerError::NoError: + _result = Success; + break; + case AssetServerError::AssetTooLarge: + _result = TooLarge; + break; + case AssetServerError::PermissionDenied: + _result = PermissionDenied; + break; + default: + _result = ErrorLoadingFile; + break; + } + emit finished(this, hash); + }); + } else { + // we couldn't open the file - set the error result + _result = ErrorLoadingFile; + + // emit that we are done + emit finished(this, QString()); + } +} diff --git a/libraries/networking/src/AssetUpload.h b/libraries/networking/src/AssetUpload.h new file mode 100644 index 0000000000..0fef814a07 --- /dev/null +++ b/libraries/networking/src/AssetUpload.h @@ -0,0 +1,55 @@ +// +// AssetUpload.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2015-08-26. +// 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 +// + +#pragma once + +#ifndef hifi_AssetUpload_h +#define hifi_AssetUpload_h + +#include + +#include + +// You should be able to upload an asset from any thread, and handle the responses in a safe way +// on your own thread. Everything should happen on AssetClient's thread, the caller should +// receive events by connecting to signals on an object that lives on AssetClient's threads. + +class AssetUpload : public QObject { + Q_OBJECT +public: + + enum Result { + Success = 0, + Timeout, + TooLarge, + PermissionDenied, + ErrorLoadingFile + }; + + AssetUpload(QObject* parent, const QString& filename); + + Q_INVOKABLE void start(); + + const QString& getFilename() const { return _filename; } + const QString& getExtension() const { return _extension; } + const Result& getResult() const { return _result; } + +signals: + void finished(AssetUpload* upload, const QString& hash); + void progress(uint64_t totalReceived, uint64_t total); + +private: + QString _filename; + QString _extension; + Result _result; +}; + +#endif // hifi_AssetUpload_h diff --git a/libraries/networking/src/AssetUtils.h b/libraries/networking/src/AssetUtils.h new file mode 100644 index 0000000000..7281678a10 --- /dev/null +++ b/libraries/networking/src/AssetUtils.h @@ -0,0 +1,38 @@ +// +// AssetUtils.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/30 +// 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 +// + +#ifndef hifi_AssetUtils_h +#define hifi_AssetUtils_h + +#include + +#include + +using MessageID = uint32_t; +using DataOffset = int64_t; + +const size_t SHA256_HASH_LENGTH = 32; +const size_t SHA256_HASH_HEX_LENGTH = 64; +const uint64_t MAX_UPLOAD_SIZE = 1000 * 1000 * 1000; // 1GB + +enum AssetServerError : uint8_t { + NoError, + AssetNotFound, + InvalidByteRange, + AssetTooLarge, + PermissionDenied +}; + +const QString ATP_SCHEME = "atp"; + +inline QByteArray hashData(const QByteArray& data) { return QCryptographicHash::hash(data, QCryptographicHash::Sha256); } + +#endif diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp index b63373858e..66c6bf2a2c 100644 --- a/libraries/networking/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -28,6 +28,8 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { return Assignment::AgentType; case NodeType::EntityServer: return Assignment::EntityServerType; + case NodeType::AssetServer: + return Assignment::AssetServerType; default: return Assignment::AllTypes; } @@ -125,6 +127,8 @@ const char* Assignment::getTypeName() const { return "avatar-mixer"; case Assignment::AgentType: return "agent"; + case Assignment::AssetServerType: + return "asset-server"; case Assignment::EntityServerType: return "entity-server"; default: @@ -144,16 +148,10 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) { } QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { - out << (quint8) assignment._type; + out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload; if (assignment._command == Assignment::RequestCommand) { - out << assignment._nodeVersion; - } - - out << assignment._uuid << assignment._pool << assignment._payload; - - if (assignment._command == Assignment::RequestCommand) { - out << assignment._walletUUID; + out << assignment._nodeVersion << assignment._walletUUID; } return out; @@ -161,17 +159,19 @@ QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { QDataStream& operator>>(QDataStream &in, Assignment& assignment) { quint8 packedType; - in >> packedType; - if (assignment._command == Assignment::RequestCommand) { - in >> assignment._nodeVersion; - } + in >> packedType >> assignment._uuid >> assignment._pool >> assignment._payload; assignment._type = (Assignment::Type) packedType; - in >> assignment._uuid >> assignment._pool >> assignment._payload; - if (assignment._command == Assignment::RequestCommand) { - in >> assignment._walletUUID; + in >> assignment._nodeVersion >> assignment._walletUUID; } return in; } + + +uint qHash(const Assignment::Type& key, uint seed) { + // seems odd that Qt couldn't figure out this cast itself, but this fixes a compile error after switch to + // strongly typed enum for PacketType + return qHash((uint8_t) key, seed); +} diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h index 6950223a9d..ee3d9cb5fd 100644 --- a/libraries/networking/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -25,13 +25,13 @@ class Assignment : public NodeData { Q_OBJECT public: - enum Type { + enum Type : uint8_t { AudioMixerType = 0, AvatarMixerType = 1, AgentType = 2, - UNUSED_0 = 3, - UNUSED_1 = 4, - UNUSED_2 = 5, + AssetServerType = 3, + UNUSED_0 = 4, + UNUSED_1 = 5, EntityServerType = 6, AllTypes = 7 }; @@ -103,4 +103,6 @@ protected: QString _nodeVersion; }; +uint qHash(const Assignment::Type& key, uint seed); + #endif // hifi_Assignment_h diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 0628e21574..5d633a8df1 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -16,7 +16,8 @@ #include #include -#include "UUID.h" +#include + #include "NetworkLogging.h" #include "DataServerAccountInfo.h" diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 8f4b9cc61f..df024b361d 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -17,7 +17,9 @@ #include "Assignment.h" #include "HifiSockAddr.h" #include "NodeList.h" +#include "udt/Packet.h" #include "udt/PacketHeaders.h" +#include "NLPacket.h" #include "SharedUtil.h" #include "UserActivityLogger.h" #include "NetworkLogging.h" @@ -38,6 +40,8 @@ DomainHandler::DomainHandler(QObject* parent) : _settingsObject(), _failedSettingsRequests(0) { + _sockAddr.setObjectName("DomainServer"); + // if we get a socket that make sure our NetworkPeer ping timer stops connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); } @@ -147,6 +151,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr; replaceableSockAddr->~HifiSockAddr(); replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); + _iceServerSockAddr.setObjectName("IceServer"); auto nodeList = DependencyManager::get(); @@ -222,6 +227,9 @@ void DomainHandler::setIsConnected(bool isConnected) { } void DomainHandler::requestDomainSettings() { + // TODO: the nodes basically lock if they don't get a response - add a timeout to this so that they at least restart + // if they can't get settings + NodeType_t owningNodeType = DependencyManager::get()->getOwnerType(); if (owningNodeType == NodeType::Agent) { // for now the agent nodes don't need any settings - this allows local assignment-clients @@ -230,57 +238,30 @@ void DomainHandler::requestDomainSettings() { emit settingsReceived(_settingsObject); } else { if (_settingsObject.isEmpty()) { - // setup the URL required to grab settings JSON - QUrl settingsJSONURL; - settingsJSONURL.setScheme("http"); - settingsJSONURL.setHost(_hostname); - settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT); - settingsJSONURL.setPath("/settings.json"); + qCDebug(networking) << "Requesting settings from domain server"; + Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType()); - settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType)); - qCDebug(networking) << "Requesting domain-server settings at" << settingsJSONURL.toString(); + auto packet = NLPacket::create(PacketType::DomainSettingsRequest, sizeof(assignmentType), true, false); + packet->writePrimitive(assignmentType); - QNetworkRequest settingsRequest(settingsJSONURL); - settingsRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = NetworkAccessManager::getInstance().get(settingsRequest); - connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished); + auto nodeList = DependencyManager::get(); + nodeList->sendPacket(std::move(packet), _sockAddr); } } } -const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5; +void DomainHandler::processSettingsPacketList(QSharedPointer packetList) { + auto data = packetList->getMessage(); -void DomainHandler::settingsRequestFinished() { - QNetworkReply* settingsReply = reinterpret_cast(sender()); + _settingsObject = QJsonDocument::fromJson(data).object(); - int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + qCDebug(networking) << "Received domain settings: \n" << QString(data); - if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) { - // parse the JSON to a QJsonObject and save it - _settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object(); + // reset failed settings requests to 0, we got them + _failedSettingsRequests = 0; - qCDebug(networking) << "Received domain settings."; - emit settingsReceived(_settingsObject); - - // reset failed settings requests to 0, we got them - _failedSettingsRequests = 0; - } else { - // error grabbing the settings - in some cases this means we are stuck - // so we should retry until we get it - qCDebug(networking) << "Error getting domain settings -" << settingsReply->errorString() << "- retrying"; - - if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) { - qCDebug(networking) << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS - << "times. Re-setting connection to domain."; - clearSettings(); - clearConnectionInfo(); - emit settingsReceiveFail(); - } else { - requestDomainSettings(); - } - } - settingsReply->deleteLater(); + emit settingsReceived(_settingsObject); } void DomainHandler::processICEPingReplyPacket(QSharedPointer packet) { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 7bb0582914..9dd4254c30 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -22,6 +22,8 @@ #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NLPacket.h" +#include "NLPacketList.h" +#include "Node.h" const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; @@ -85,6 +87,7 @@ public slots: void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT); void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id); + void processSettingsPacketList(QSharedPointer packetList); void processICEPingReplyPacket(QSharedPointer packet); void processDTLSRequirementPacket(QSharedPointer dtlsRequirementPacket); void processICEResponsePacket(QSharedPointer icePacket); @@ -92,7 +95,6 @@ public slots: private slots: void completedHostnameLookup(const QHostInfo& hostInfo); void completedIceServerHostnameLookup(); - void settingsRequestFinished(); signals: void hostnameChanged(const QString& hostname); diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp new file mode 100644 index 0000000000..58a2074103 --- /dev/null +++ b/libraries/networking/src/FileResourceRequest.cpp @@ -0,0 +1,39 @@ +// +// FileResourceRequest.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 "FileResourceRequest.h" + +#include + +void FileResourceRequest::doSend() { + QString filename = _url.toLocalFile(); + + // sometimes on windows, we see the toLocalFile() return null, + // in this case we will attempt to simply use the url as a string + if (filename.isEmpty()) { + filename = _url.toString(); + } + + QFile file(filename); + if (file.exists()) { + if (file.open(QFile::ReadOnly)) { + _data = file.readAll(); + _result = ResourceRequest::Success; + } else { + _result = ResourceRequest::AccessDenied; + } + } else { + _result = ResourceRequest::NotFound; + } + + _state = Finished; + emit finished(); +} diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h new file mode 100644 index 0000000000..4ff0d2ecf2 --- /dev/null +++ b/libraries/networking/src/FileResourceRequest.h @@ -0,0 +1,28 @@ +// +// FileResourceRequest.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 +// + +#ifndef hifi_FileResourceRequest_h +#define hifi_FileResourceRequest_h + +#include + +#include "ResourceRequest.h" + +class FileResourceRequest : public ResourceRequest { + Q_OBJECT +public: + FileResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + +protected: + virtual void doSend() override; +}; + +#endif diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp new file mode 100644 index 0000000000..fd650df348 --- /dev/null +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -0,0 +1,99 @@ +// +// HTTPResourceRequest.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 "HTTPResourceRequest.h" + +#include +#include +#include + +#include + +#include "NetworkAccessManager.h" +#include "NetworkLogging.h" + +HTTPResourceRequest::~HTTPResourceRequest() { + if (_reply) { + _reply->disconnect(this); + _reply->deleteLater(); + _reply = nullptr; + } +} + +void HTTPResourceRequest::doSend() { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest = QNetworkRequest(_url); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + + if (_cacheEnabled) { + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + } else { + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + } + + _reply = networkAccessManager.get(networkRequest); + + connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished); + connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress); + connect(&_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout); + + static const int TIMEOUT_MS = 10000; + _sendTimer.setSingleShot(true); + _sendTimer.start(TIMEOUT_MS); +} + +void HTTPResourceRequest::onRequestFinished() { + Q_ASSERT(_state == InProgress); + Q_ASSERT(_reply); + + _sendTimer.stop(); + + switch(_reply->error()) { + case QNetworkReply::NoError: + _data = _reply->readAll(); + _loadedFromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); + _result = Success; + break; + case QNetworkReply::TimeoutError: + _result = Timeout; + break; + default: + _result = Error; + break; + } + _reply->disconnect(this); + _reply->deleteLater(); + _reply = nullptr; + + _state = Finished; + emit finished(); +} + +void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + Q_ASSERT(_state == InProgress); + + // We've received data, so reset the timer + _sendTimer.start(); + + emit progress(bytesReceived, bytesTotal); +} + +void HTTPResourceRequest::onTimeout() { + Q_ASSERT(_state == InProgress); + _reply->disconnect(this); + _reply->abort(); + _reply->deleteLater(); + _reply = nullptr; + + _result = Timeout; + _state = Finished; + emit finished(); +} diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h new file mode 100644 index 0000000000..f0d3bb82d9 --- /dev/null +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -0,0 +1,40 @@ +// +// HTTPResourceRequest.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 +// + +#ifndef hifi_HTTPResourceRequest_h +#define hifi_HTTPResourceRequest_h + +#include +#include +#include + +#include "ResourceRequest.h" + +class HTTPResourceRequest : public ResourceRequest { + Q_OBJECT +public: + HTTPResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } + ~HTTPResourceRequest(); + +protected: + virtual void doSend() override; + +private slots: + void onTimeout(); + void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void onRequestFinished(); + +private: + QTimer _sendTimer; + QNetworkReply* _reply { nullptr }; +}; + +#endif diff --git a/libraries/networking/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp index 8951da58c9..813d19d22c 100644 --- a/libraries/networking/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -33,16 +33,16 @@ HifiSockAddr::HifiSockAddr(const QHostAddress& address, quint16 port) : } HifiSockAddr::HifiSockAddr(const HifiSockAddr& otherSockAddr) : - QObject(), _address(otherSockAddr._address), _port(otherSockAddr._port) { - + setObjectName(otherSockAddr.objectName()); } HifiSockAddr& HifiSockAddr::operator=(const HifiSockAddr& rhsSockAddr) { - HifiSockAddr temp(rhsSockAddr); - swap(temp); + setObjectName(rhsSockAddr.objectName()); + _address = rhsSockAddr._address; + _port = rhsSockAddr._port; return *this; } @@ -76,9 +76,14 @@ HifiSockAddr::HifiSockAddr(const sockaddr* sockaddr) { void HifiSockAddr::swap(HifiSockAddr& otherSockAddr) { using std::swap; - + swap(_address, otherSockAddr._address); swap(_port, otherSockAddr._port); + + // Swap objects name + auto temp = otherSockAddr.objectName(); + otherSockAddr.setObjectName(objectName()); + setObjectName(temp); } bool HifiSockAddr::operator==(const HifiSockAddr& rhsSockAddr) const { @@ -150,5 +155,5 @@ QHostAddress getLocalAddress() { 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); + return qHash(key.getAddress(), seed) ^ qHash(key.getPort(), seed); } diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index 9151e51af2..d678f93ac0 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -12,6 +12,9 @@ #ifndef hifi_HifiSockAddr_h #define hifi_HifiSockAddr_h +#include +#include + #ifdef WIN32 #include #include @@ -53,6 +56,7 @@ public: friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr); friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr); friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr); + private slots: void handleLookupResult(const QHostInfo& hostInfo); signals: @@ -65,6 +69,29 @@ private: uint qHash(const HifiSockAddr& key, uint seed); +namespace std { + template <> + struct hash { + // NOTE: this hashing specifically ignores IPv6 addresses - if we begin to support those we will need + // to conditionally hash the bytes that represent an IPv6 address + size_t operator()(const HifiSockAddr& sockAddr) const { + // use XOR of implemented std::hash templates for new hash + // depending on the type of address we're looking at + + if (sockAddr.getAddress().protocol() == QAbstractSocket::IPv4Protocol) { + return hash()((uint32_t) sockAddr.getAddress().toIPv4Address()) + ^ hash()((uint16_t) sockAddr.getPort()); + } else { + // NOTE: if we start to use IPv6 addresses, it's possible their hashing + // can be faster by XORing the hash for each 64 bits in the address + return hash()(sockAddr.getAddress().toString().toStdString()) + ^ hash()((uint16_t) sockAddr.getPort()); + } + } + }; +} + + QHostAddress getLocalAddress(); Q_DECLARE_METATYPE(HifiSockAddr) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 48febf39bd..38c87418c8 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -26,14 +26,13 @@ #include #include #include +#include #include "AccountManager.h" #include "Assignment.h" #include "HifiSockAddr.h" -#include "UUID.h" #include "NetworkLogging.h" - -#include "udt/udt.h" +#include "udt/Packet.h" const char SOLO_NODE_TYPES[2] = { NodeType::AvatarMixer, @@ -67,7 +66,7 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short firstCall = false; } - + qRegisterMetaType("ConnectionStep"); _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); @@ -81,9 +80,6 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); } - const int LARGER_BUFFER_SIZE = 1048576; - changeSocketBufferSizes(LARGER_BUFFER_SIZE); - // check for local socket updates every so often const int LOCAL_SOCKET_UPDATE_INTERVAL_MSECS = 5 * 1000; QTimer* localSocketUpdate = new QTimer(this); @@ -96,15 +92,24 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short // check the local socket right now updateLocalSockAddr(); - - // TODO: Create a new thread, and move PacketReceiver to it - - connect(&_nodeSocket, &QUdpSocket::readyRead, _packetReceiver, &PacketReceiver::processDatagrams); - - _packetStatTimer.start(); - // make sure we handle STUN response packets - _packetReceiver->registerListener(PacketType::StunResponse, this, "processSTUNResponse"); + // set &PacketReceiver::handleVerifiedPacket as the verified packet callback for the udt::Socket + _nodeSocket.setPacketHandler( + [this](std::unique_ptr packet) { + _packetReceiver->handleVerifiedPacket(std::move(packet)); + } + ); + _nodeSocket.setPacketListHandler( + [this](std::unique_ptr packetList) { + _packetReceiver->handleVerifiedPacketList(std::move(packetList)); + } + ); + + // set our isPacketVerified method as the verify operator for the udt::Socket + using std::placeholders::_1; + _nodeSocket.setPacketFilterOperator(std::bind(&LimitedNodeList::isPacketVerified, this, _1)); + + _packetStatTimer.start(); } void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { @@ -149,58 +154,90 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { return *_dtlsSocket; } -void LimitedNodeList::changeSocketBufferSizes(int numBytes) { - for (int i = 0; i < 2; i++) { - QAbstractSocket::SocketOption bufferOpt; - QString bufferTypeString; - if (i == 0) { - bufferOpt = QAbstractSocket::SendBufferSizeSocketOption; - bufferTypeString = "send"; +bool LimitedNodeList::isPacketVerified(const udt::Packet& packet) { + return packetVersionMatch(packet) && packetSourceAndHashMatch(packet); +} +bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { + PacketType headerType = NLPacket::typeInHeader(packet); + PacketVersion headerVersion = NLPacket::versionInHeader(packet); + + if (headerVersion != versionForPacketType(headerType)) { + + static QMultiHash sourcedVersionDebugSuppressMap; + static QMultiHash versionDebugSuppressMap; + + bool hasBeenOutput = false; + QString senderString; + + if (NON_SOURCED_PACKETS.contains(headerType)) { + const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); + hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, headerType); + + if (!hasBeenOutput) { + versionDebugSuppressMap.insert(senderSockAddr, headerType); + senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort()); + } } else { - bufferOpt = QAbstractSocket::ReceiveBufferSizeSocketOption; - bufferTypeString = "receive"; + QUuid sourceID = NLPacket::sourceIDInHeader(packet); + + hasBeenOutput = sourcedVersionDebugSuppressMap.contains(sourceID, headerType); + + if (!hasBeenOutput) { + sourcedVersionDebugSuppressMap.insert(sourceID, headerType); + senderString = uuidStringWithoutCurlyBraces(sourceID.toString()); + } } - int oldBufferSize = _nodeSocket.socketOption(bufferOpt).toInt(); - if (oldBufferSize < numBytes) { - int newBufferSize = _nodeSocket.socketOption(bufferOpt).toInt(); - - qCDebug(networking) << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to" - << newBufferSize << "bytes"; - } else { - // don't make the buffer smaller - qCDebug(networking) << "Did not change socket" << bufferTypeString << "buffer size from" << oldBufferSize - << "since it is larger than desired size of" << numBytes; + + if (!hasBeenOutput) { + qCDebug(networking) << "Packet version mismatch on" << headerType << "- Sender" + << senderString << "sent" << qPrintable(QString::number(headerVersion)) << "but" + << qPrintable(QString::number(versionForPacketType(headerType))) << "expected."; + + emit packetVersionMismatch(headerType); } + + return false; + } else { + return true; } } -bool LimitedNodeList::packetSourceAndHashMatch(const NLPacket& packet, SharedNodePointer& matchingNode) { +bool LimitedNodeList::packetSourceAndHashMatch(const udt::Packet& packet) { - if (NON_SOURCED_PACKETS.contains(packet.getType())) { + PacketType headerType = NLPacket::typeInHeader(packet); + + if (NON_SOURCED_PACKETS.contains(headerType)) { return true; } else { + QUuid sourceID = NLPacket::sourceIDInHeader(packet); // figure out which node this is from - matchingNode = nodeWithUUID(packet.getSourceID()); + SharedNodePointer matchingNode = nodeWithUUID(sourceID); if (matchingNode) { - if (!NON_VERIFIED_PACKETS.contains(packet.getType())) { + if (!NON_VERIFIED_PACKETS.contains(headerType)) { + + QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); + QByteArray expectedHash = NLPacket::hashForPacketAndSecret(packet, matchingNode->getConnectionSecret()); + // check if the md5 hash in the header matches the hash we would expect - if (packet.getVerificationHash() != packet.payloadHashWithConnectionUUID(matchingNode->getConnectionSecret())) { - static QMultiMap hashDebugSuppressMap; + if (packetHeaderHash != expectedHash) { + static QMultiMap hashDebugSuppressMap; - const QUuid& senderID = packet.getSourceID(); + if (!hashDebugSuppressMap.contains(sourceID, headerType)) { + qCDebug(networking) << "Packet hash mismatch on" << headerType << "- Sender" << sourceID; - if (!hashDebugSuppressMap.contains(senderID, packet.getType())) { - qCDebug(networking) << "Packet hash mismatch on" << packet.getType() << "- Sender" << senderID; - - hashDebugSuppressMap.insert(senderID, packet.getType()); + hashDebugSuppressMap.insert(sourceID, headerType); } return false; } } + + // No matter if this packet is handled or not, we update the timestamp for the last time we heard + // from this sending node + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); return true; @@ -208,95 +245,94 @@ bool LimitedNodeList::packetSourceAndHashMatch(const NLPacket& packet, SharedNod static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("Packet of type \\d+ \\([\\sa-zA-Z]+\\) received from unknown node with UUID"); - qCDebug(networking) << "Packet of type" << packet.getType() << "(" << qPrintable(nameForPacketType(packet.getType())) << ")" - << "received from unknown node with UUID" << qPrintable(uuidStringWithoutCurlyBraces(packet.getSourceID())); + qCDebug(networking) << "Packet of type" << headerType + << "received from unknown node with UUID" << qPrintable(uuidStringWithoutCurlyBraces(sourceID)); } } return false; } -qint64 LimitedNodeList::writePacket(const NLPacket& packet, const Node& destinationNode) { - if (!destinationNode.getActiveSocket()) { - return 0; - } - - // TODO Move to transport layer when ready - if (SEQUENCE_NUMBERED_PACKETS.contains(packet.getType())) { - PacketSequenceNumber sequenceNumber = getNextSequenceNumberForPacket(destinationNode.getUUID(), packet.getType()); - const_cast(packet).writeSequenceNumber(sequenceNumber); - } - - emit dataSent(destinationNode.getType(), packet.getDataSize()); - - return writePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret()); +void LimitedNodeList::collectPacketStats(const NLPacket& packet) { + // stat collection for packets + ++_numCollectedPackets; + _numCollectedBytes += packet.getDataSize(); } -qint64 LimitedNodeList::writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr, - const QUuid& connectionSecret) { +void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret) { if (!NON_SOURCED_PACKETS.contains(packet.getType())) { - const_cast(packet).writeSourceID(getSessionUUID()); + packet.writeSourceID(getSessionUUID()); } if (!connectionSecret.isNull() && !NON_SOURCED_PACKETS.contains(packet.getType()) && !NON_VERIFIED_PACKETS.contains(packet.getType())) { - const_cast(packet).writeVerificationHash(packet.payloadHashWithConnectionUUID(connectionSecret)); + packet.writeVerificationHashGivenSecret(connectionSecret); } - - emit dataSent(NodeType::Unassigned, packet.getDataSize()); - - return writeDatagram(QByteArray::fromRawData(packet.getData(), packet.getDataSize()), destinationSockAddr); -} - -qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr) { - // XXX can BandwidthRecorder be used for this? - // stat collection for packets - ++_numCollectedPackets; - _numCollectedBytes += datagram.size(); - - qint64 bytesWritten = _nodeSocket.writeDatagram(datagram, - destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - - if (bytesWritten < 0) { - qCDebug(networking) << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); - } - - return bytesWritten; } qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode) { - return writePacket(packet, destinationNode); + Q_ASSERT(!packet.isPartOfMessage()); + if (!destinationNode.getActiveSocket()) { + return 0; + } + emit dataSent(destinationNode.getType(), packet.getDataSize()); + return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret()); } qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiSockAddr& sockAddr, const QUuid& connectionSecret) { - return writePacket(packet, sockAddr, connectionSecret); + Q_ASSERT(!packet.isPartOfMessage()); + Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", + "Trying to send a reliable packet unreliably."); + + collectPacketStats(packet); + fillPacketHeader(packet, connectionSecret); + + return _nodeSocket.writePacket(packet, sockAddr); } qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& destinationNode) { - // Keep unique_ptr alive during write - auto result = writePacket(*packet, destinationNode); - return result; + Q_ASSERT(!packet->isPartOfMessage()); + if (!destinationNode.getActiveSocket()) { + return 0; + } + emit dataSent(destinationNode.getType(), packet->getDataSize()); + return sendPacket(std::move(packet), *destinationNode.getActiveSocket(), destinationNode.getConnectionSecret()); } qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const HifiSockAddr& sockAddr, const QUuid& connectionSecret) { - // Keep unique_ptr alive during write - auto result = writePacket(*packet, sockAddr, connectionSecret); - return result; + Q_ASSERT(!packet->isPartOfMessage()); + if (packet->isReliable()) { + collectPacketStats(*packet); + fillPacketHeader(*packet, connectionSecret); + + auto size = packet->getDataSize(); + _nodeSocket.writePacket(std::move(packet), sockAddr); + + return size; + } else { + return sendUnreliablePacket(*packet, sockAddr, connectionSecret); + } } qint64 LimitedNodeList::sendPacketList(NLPacketList& packetList, const Node& destinationNode) { + auto activeSocket = destinationNode.getActiveSocket(); + if (!activeSocket) { + return 0; + } qint64 bytesSent = 0; + auto connectionSecret = destinationNode.getConnectionSecret(); // close the last packet in the list packetList.closeCurrentPacket(); while (!packetList._packets.empty()) { - bytesSent += sendPacket(packetList.takeFront(), destinationNode); + bytesSent += sendPacket(packetList.takeFront(), *activeSocket, connectionSecret); } + emit dataSent(destinationNode.getType(), bytesSent); return bytesSent; } @@ -314,6 +350,26 @@ qint64 LimitedNodeList::sendPacketList(NLPacketList& packetList, const HifiSockA return bytesSent; } +qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, const HifiSockAddr& sockAddr) { + // close the last packet in the list + packetList->closeCurrentPacket(); + + return _nodeSocket.writePacketList(std::move(packetList), sockAddr); +} + +qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, const Node& destinationNode) { + // close the last packet in the list + packetList->closeCurrentPacket(); + + for (std::unique_ptr& packet : packetList->_packets) { + NLPacket* nlPacket = static_cast(packet.get()); + collectPacketStats(*nlPacket); + fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret()); + } + + return _nodeSocket.writePacketList(std::move(packetList), *destinationNode.getActiveSocket()); +} + qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& destinationNode, const HifiSockAddr& overridenSockAddr) { // use the node's active socket as the destination socket if there is no overriden socket address @@ -322,15 +378,6 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& return sendPacket(std::move(packet), destinationSockAddr, destinationNode.getConnectionSecret()); } -PacketSequenceNumber LimitedNodeList::getNextSequenceNumberForPacket(const QUuid& nodeUUID, PacketType::Value packetType) { - // Thanks to std::map and std::unordered_map this line either default constructs the - // PacketType::SequenceMap and the PacketSequenceNumber or returns the existing value. - // We use the postfix increment so that the stored value is incremented and the next - // return gives the correct value. - - return _packetSequenceNumbers[nodeUUID][packetType]++; -} - int LimitedNodeList::updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer sendingNode) { QMutexLocker locker(&sendingNode->getMutex()); @@ -374,6 +421,9 @@ void LimitedNodeList::eraseAllNodes() { void LimitedNodeList::reset() { eraseAllNodes(); + + // we need to make sure any socket connections are gone so wait on that here + _nodeSocket.clearConnections(); } void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { @@ -407,6 +457,10 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { qCDebug(networking) << "Killed" << *node; node->stopPingTimer(); emit nodeKilled(node); + + if (auto activeSocket = node->getActiveSocket()) { + _nodeSocket.cleanupConnection(*activeSocket); + } } SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, @@ -502,7 +556,7 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr packet, eachNode([&](const SharedNodePointer& node){ if (node && destinationNodeTypes.contains(node->getType())) { - writePacket(*packet, *node); + sendUnreliablePacket(*packet, *node); ++n; } }); @@ -510,7 +564,7 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr packet, return n; } -SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) { +SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) { return nodeMatchingPredicate([&](const SharedNodePointer& node){ return node->getType() == nodeType; }); @@ -571,7 +625,7 @@ void LimitedNodeList::sendSTUNRequest() { ++_numInitialSTUNRequests; } - unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; + char stunRequestPacket[NUM_BYTES_STUN_HEADER]; int packetIndex = 0; @@ -597,18 +651,10 @@ void LimitedNodeList::sendSTUNRequest() { flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest); - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - _stunSockAddr.getAddress(), _stunSockAddr.getPort()); + _nodeSocket.writeDatagram(stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr); } -void LimitedNodeList::rebindNodeSocket() { - quint16 oldPort = _nodeSocket.localPort(); - - _nodeSocket.close(); - _nodeSocket.bind(QHostAddress::AnyIPv4, oldPort); -} - -bool LimitedNodeList::processSTUNResponse(QSharedPointer packet) { +void LimitedNodeList::processSTUNResponse(std::unique_ptr 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; @@ -624,6 +670,7 @@ bool LimitedNodeList::processSTUNResponse(QSharedPointer packet) { // 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; @@ -651,8 +698,8 @@ bool LimitedNodeList::processSTUNResponse(QSharedPointer packet) { uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - QHostAddress newPublicAddress = QHostAddress(stunAddress); - + QHostAddress newPublicAddress(stunAddress); + if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); @@ -669,8 +716,9 @@ bool LimitedNodeList::processSTUNResponse(QSharedPointer packet) { flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); } - - return true; + + // we're done reading the packet so we can return now + return; } } else { // push forward attributeStartIndex by the length of this attribute @@ -685,8 +733,6 @@ bool LimitedNodeList::processSTUNResponse(QSharedPointer packet) { } } } - - return false; } void LimitedNodeList::startSTUNPublicSocketUpdate() { @@ -696,6 +742,7 @@ void LimitedNodeList::startSTUNPublicSocketUpdate() { // if we don't know the STUN IP yet we need to have ourselves be called once it is known if (_stunSockAddr.getAddress().isNull()) { connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::startSTUNPublicSocketUpdate); + connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::addSTUNHandlerToUnfiltered); // in case we just completely fail to lookup the stun socket - add a 10s timeout that will trigger the fail case const quint64 STUN_DNS_LOOKUP_TIMEOUT_MSECS = 10 * 1000; @@ -726,6 +773,11 @@ void LimitedNodeList::possiblyTimeoutSTUNAddressLookup() { } } +void LimitedNodeList::addSTUNHandlerToUnfiltered() { + // make ourselves the handler of STUN packets when they come in + _nodeSocket.addUnfilteredHandler(_stunSockAddr, [this](std::unique_ptr packet) { processSTUNResponse(std::move(packet)); }); +} + void LimitedNodeList::stopInitialSTUNUpdate(bool success) { _hasCompletedInitialSTUN = true; @@ -786,7 +838,7 @@ void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSock sendPacketToIceServer(PacketType::ICEServerQuery, iceServerSockAddr, clientID, peerID); } -void LimitedNodeList::sendPacketToIceServer(PacketType::Value packetType, const HifiSockAddr& iceServerSockAddr, +void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID) { auto icePacket = NLPacket::create(packetType); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 88a3ccf8ff..f1d4887b70 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -37,9 +37,10 @@ #include "DomainHandler.h" #include "Node.h" #include "NLPacket.h" -#include "udt/PacketHeaders.h" #include "PacketReceiver.h" #include "NLPacketList.h" +#include "udt/PacketHeaders.h" +#include "udt/Socket.h" #include "UUIDHasher.h" const quint64 NODE_SILENCE_THRESHOLD_MSECS = 2 * 1000; @@ -109,13 +110,10 @@ public: bool getThisNodeCanRez() const { return _thisNodeCanRez; } void setThisNodeCanRez(bool canRez); - - void rebindNodeSocket(); - QUdpSocket& getNodeSocket() { return _nodeSocket; } + + quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } QUdpSocket& getDTLSSocket(); - bool packetSourceAndHashMatch(const NLPacket& packet, SharedNodePointer& matchingNode); - PacketReceiver& getPacketReceiver() { return *_packetReceiver; } qint64 sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode); @@ -129,6 +127,8 @@ public: qint64 sendPacketList(NLPacketList& packetList, const Node& destinationNode); qint64 sendPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, const QUuid& connectionSecret = QUuid()); + qint64 sendPacketList(std::unique_ptr packetList, const HifiSockAddr& sockAddr); + qint64 sendPacketList(std::unique_ptr packetList, const Node& destinationNode); void (*linkedDataCreateCallback)(Node *); @@ -151,7 +151,7 @@ public: int updateNodeWithDataFromPacket(QSharedPointer packet, SharedNodePointer matchingNode); unsigned int broadcastToNodes(std::unique_ptr packet, const NodeSet& destinationNodeTypes); - SharedNodePointer soloNodeOfType(char nodeType); + SharedNodePointer soloNodeOfType(NodeType_t nodeType); void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); @@ -227,12 +227,12 @@ public slots: void startSTUNPublicSocketUpdate(); virtual void sendSTUNRequest(); - bool processSTUNResponse(QSharedPointer packet); void killNodeWithUUID(const QUuid& nodeUUID); signals: void dataSent(quint8 channelType, int bytes); + void packetVersionMismatch(PacketType type); void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); void nodeAdded(SharedNodePointer); @@ -249,30 +249,31 @@ protected: 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 writePacket(const NLPacket& packet, const Node& destinationNode); + qint64 sendPacket(std::unique_ptr packet, const Node& destinationNode, + const HifiSockAddr& overridenSockAddr); qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr, const QUuid& connectionSecret = QUuid()); - qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr); - - PacketSequenceNumber getNextSequenceNumberForPacket(const QUuid& nodeUUID, PacketType::Value packetType); - - void changeSocketBufferSizes(int numBytes); + void collectPacketStats(const NLPacket& packet); + void fillPacketHeader(const NLPacket& packet, const QUuid& connectionSecret); + + bool isPacketVerified(const udt::Packet& packet); + bool packetVersionMatch(const udt::Packet& packet); + bool packetSourceAndHashMatch(const udt::Packet& packet); + void processSTUNResponse(std::unique_ptr packet); void handleNodeKill(const SharedNodePointer& node); void stopInitialSTUNUpdate(bool success); - void sendPacketToIceServer(PacketType::Value packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, + void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerRequestID = QUuid()); - qint64 sendPacket(std::unique_ptr packet, const Node& destinationNode, - const HifiSockAddr& overridenSockAddr); QUuid _sessionUUID; NodeHash _nodeHash; QReadWriteLock _nodeMutex; - QUdpSocket _nodeSocket; + udt::Socket _nodeSocket; QUdpSocket* _dtlsSocket; HifiSockAddr _localSockAddr; HifiSockAddr _publicSockAddr; @@ -288,8 +289,6 @@ protected: bool _thisNodeCanAdjustLocks; bool _thisNodeCanRez; - std::unordered_map _packetSequenceNumbers; - QPointer _initialSTUNTimer; int _numInitialSTUNRequests = 0; bool _hasCompletedInitialSTUN = false; @@ -309,9 +308,11 @@ protected: functor(it); } } + private slots: void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp); void possiblyTimeoutSTUNAddressLookup(); + void addSTUNHandlerToUnfiltered(); // called once STUN socket known }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index 7ec10fc4ce..575a2c7a9c 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -11,35 +11,21 @@ #include "NLPacket.h" -qint64 NLPacket::localHeaderSize(PacketType::Value type) { - qint64 size = ((NON_SOURCED_PACKETS.contains(type)) ? 0 : NUM_BYTES_RFC4122_UUID) + - ((NON_SOURCED_PACKETS.contains(type) || NON_VERIFIED_PACKETS.contains(type)) ? 0 : NUM_BYTES_MD5_HASH); - return size; +int NLPacket::localHeaderSize(PacketType type) { + bool nonSourced = NON_SOURCED_PACKETS.contains(type); + bool nonVerified = NON_VERIFIED_PACKETS.contains(type); + qint64 optionalSize = (nonSourced ? 0 : NUM_BYTES_RFC4122_UUID) + ((nonSourced || nonVerified) ? 0 : NUM_BYTES_MD5_HASH); + return sizeof(PacketType) + sizeof(PacketVersion) + optionalSize; +} +int NLPacket::totalHeaderSize(PacketType type, bool isPartOfMessage) { + return Packet::totalHeaderSize(isPartOfMessage) + NLPacket::localHeaderSize(type); +} +int NLPacket::maxPayloadSize(PacketType type, bool isPartOfMessage) { + return Packet::maxPayloadSize(isPartOfMessage) - NLPacket::localHeaderSize(type); } -qint64 NLPacket::maxPayloadSize(PacketType::Value type) { - return Packet::maxPayloadSize(type) - localHeaderSize(type); -} - -qint64 NLPacket::totalHeadersSize() const { - return localHeaderSize() + Packet::localHeaderSize(); -} - -qint64 NLPacket::localHeaderSize() const { - return localHeaderSize(_type); -} - -std::unique_ptr NLPacket::create(PacketType::Value type, qint64 size) { - std::unique_ptr packet; - - if (size == -1) { - packet = std::unique_ptr(new NLPacket(type)); - } else { - // Fail with invalid size - Q_ASSERT(size >= 0); - - packet = std::unique_ptr(new NLPacket(type, size)); - } +std::unique_ptr NLPacket::create(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) { + auto packet = std::unique_ptr(new NLPacket(type, size, isReliable, isPartOfMessage)); packet->open(QIODevice::ReadWrite); @@ -63,83 +49,170 @@ std::unique_ptr NLPacket::fromReceivedPacket(std::unique_ptr d } +std::unique_ptr NLPacket::fromBase(std::unique_ptr packet) { + // Fail with null packet + Q_ASSERT(packet); + + // call our constructor to create an NLPacket from this Packet + return std::unique_ptr(new NLPacket(std::move(*packet))); +} + std::unique_ptr NLPacket::createCopy(const NLPacket& other) { return std::unique_ptr(new NLPacket(other)); } -NLPacket::NLPacket(PacketType::Value type, qint64 size) : - Packet(type, localHeaderSize(type) + size) +NLPacket::NLPacket(PacketType type, qint64 size, bool isReliable, bool isPartOfMessage) : + Packet((size == -1) ? -1 : NLPacket::localHeaderSize(type) + size, isReliable, isPartOfMessage), + _type(type), + _version(versionForPacketType(type)) { - Q_ASSERT(size >= 0); + adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type)); - adjustPayloadStartAndCapacity(); + writeTypeAndVersion(); } -NLPacket::NLPacket(PacketType::Value type) : - Packet(type, -1) +NLPacket::NLPacket(Packet&& packet) : + Packet(std::move(packet)) { - adjustPayloadStartAndCapacity(); + readType(); + readVersion(); + readSourceID(); + + adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type), _payloadSize > 0); } NLPacket::NLPacket(const NLPacket& other) : Packet(other) { - + _type = other._type; + _version = other._version; + _sourceID = other._sourceID; } NLPacket::NLPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr) : Packet(std::move(data), size, senderSockAddr) -{ - adjustPayloadStartAndCapacity(); - _payloadSize = _payloadCapacity; +{ + // sanity check before we decrease the payloadSize with the payloadCapacity + Q_ASSERT(_payloadSize == _payloadCapacity); + readType(); + readVersion(); readSourceID(); - readVerificationHash(); + + adjustPayloadStartAndCapacity(NLPacket::localHeaderSize(_type), _payloadSize > 0); } -void NLPacket::adjustPayloadStartAndCapacity() { - qint64 headerSize = localHeaderSize(_type); - _payloadStart += headerSize; - _payloadCapacity -= headerSize; +NLPacket::NLPacket(NLPacket&& other) : + Packet(std::move(other)) +{ + _type = other._type; + _version = other._version; + _sourceID = std::move(other._sourceID); +} + +NLPacket& NLPacket::operator=(const NLPacket& other) { + Packet::operator=(other); + + _type = other._type; + _version = other._version; + _sourceID = other._sourceID; + + return *this; +} + +NLPacket& NLPacket::operator=(NLPacket&& other) { + + Packet::operator=(std::move(other)); + + _type = other._type; + _version = other._version; + _sourceID = std::move(other._sourceID); + + return *this; +} + +PacketType NLPacket::typeInHeader(const udt::Packet& packet) { + auto headerOffset = Packet::totalHeaderSize(packet.isPartOfMessage()); + return *reinterpret_cast(packet.getData() + headerOffset); +} + +PacketVersion NLPacket::versionInHeader(const udt::Packet& packet) { + auto headerOffset = Packet::totalHeaderSize(packet.isPartOfMessage()); + return *reinterpret_cast(packet.getData() + headerOffset + sizeof(PacketType)); +} + +QUuid NLPacket::sourceIDInHeader(const udt::Packet& packet) { + int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion); + return QUuid::fromRfc4122(QByteArray::fromRawData(packet.getData() + offset, NUM_BYTES_RFC4122_UUID)); +} + +QByteArray NLPacket::verificationHashInHeader(const udt::Packet& packet) { + int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; + return QByteArray(packet.getData() + offset, NUM_BYTES_MD5_HASH); +} + +QByteArray NLPacket::hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret) { + QCryptographicHash hash(QCryptographicHash::Md5); + + int offset = Packet::totalHeaderSize(packet.isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; + + // add the packet payload and the connection UUID + hash.addData(packet.getData() + offset, packet.getDataSize() - offset); + hash.addData(connectionSecret.toRfc4122()); + + // return the hash + return hash.result(); +} + +void NLPacket::writeTypeAndVersion() { + auto headerOffset = Packet::totalHeaderSize(isPartOfMessage()); + + // Pack the packet type + memcpy(_packet.get() + headerOffset, &_type, sizeof(PacketType)); + + // Pack the packet version + memcpy(_packet.get() + headerOffset + sizeof(PacketType), &_version, sizeof(_version)); +} + +void NLPacket::setType(PacketType type) { + // Setting new packet type with a different header size not currently supported + Q_ASSERT(NLPacket::totalHeaderSize(_type, isPartOfMessage()) == + NLPacket::totalHeaderSize(type, isPartOfMessage())); + + _type = type; + _version = versionForPacketType(_type); + + writeTypeAndVersion(); +} + +void NLPacket::readType() { + _type = NLPacket::typeInHeader(*this); +} + +void NLPacket::readVersion() { + _version = NLPacket::versionInHeader(*this); } void NLPacket::readSourceID() { if (!NON_SOURCED_PACKETS.contains(_type)) { - auto offset = Packet::localHeaderSize(); - _sourceID = QUuid::fromRfc4122(QByteArray::fromRawData(_packet.get() + offset, NUM_BYTES_RFC4122_UUID)); + _sourceID = sourceIDInHeader(*this); } } -void NLPacket::readVerificationHash() { - if (!NON_SOURCED_PACKETS.contains(_type) && !NON_VERIFIED_PACKETS.contains(_type)) { - auto offset = Packet::localHeaderSize() + NUM_BYTES_RFC4122_UUID; - _verificationHash = QByteArray(_packet.get() + offset, NUM_BYTES_MD5_HASH); - } -} - -void NLPacket::writeSourceID(const QUuid& sourceID) { +void NLPacket::writeSourceID(const QUuid& sourceID) const { Q_ASSERT(!NON_SOURCED_PACKETS.contains(_type)); - auto offset = Packet::localHeaderSize(); + auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion); memcpy(_packet.get() + offset, sourceID.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID); _sourceID = sourceID; } -void NLPacket::writeVerificationHash(const QByteArray& verificationHash) { +void NLPacket::writeVerificationHashGivenSecret(const QUuid& connectionSecret) const { Q_ASSERT(!NON_SOURCED_PACKETS.contains(_type) && !NON_VERIFIED_PACKETS.contains(_type)); - - auto offset = Packet::localHeaderSize() + NUM_BYTES_RFC4122_UUID; + + auto offset = Packet::totalHeaderSize(isPartOfMessage()) + sizeof(PacketType) + sizeof(PacketVersion) + + NUM_BYTES_RFC4122_UUID; + QByteArray verificationHash = hashForPacketAndSecret(*this, connectionSecret); + memcpy(_packet.get() + offset, verificationHash.data(), verificationHash.size()); - - _verificationHash = verificationHash; -} - -QByteArray NLPacket::payloadHashWithConnectionUUID(const QUuid& connectionUUID) const { - QCryptographicHash hash(QCryptographicHash::Md5); - - // add the packet payload and the connection UUID - hash.addData(_payloadStart, _payloadSize); - hash.addData(connectionUUID.toRfc4122()); - - // return the hash - return hash.result(); } diff --git a/libraries/networking/src/NLPacket.h b/libraries/networking/src/NLPacket.h index 6ebf15ffec..57ada84607 100644 --- a/libraries/networking/src/NLPacket.h +++ b/libraries/networking/src/NLPacket.h @@ -14,45 +14,75 @@ #include +#include + #include "udt/Packet.h" -class NLPacket : public Packet { +class NLPacket : public udt::Packet { Q_OBJECT public: - static std::unique_ptr create(PacketType::Value type, qint64 size = -1); + // this is used by the Octree classes - must be known at compile time + static const int MAX_PACKET_HEADER_SIZE = + sizeof(udt::Packet::SequenceNumberAndBitField) + sizeof(udt::Packet::MessageNumberAndBitField) + + sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; + + static std::unique_ptr create(PacketType type, qint64 size = -1, + bool isReliable = false, bool isPartOfMessage = false); + static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); + static std::unique_ptr fromBase(std::unique_ptr packet); + // Provided for convenience, try to limit use static std::unique_ptr createCopy(const NLPacket& other); - - static qint64 localHeaderSize(PacketType::Value type); - static qint64 maxPayloadSize(PacketType::Value type); - - virtual qint64 totalHeadersSize() const; // Cumulated size of all the headers - virtual qint64 localHeaderSize() const; // Current level's header size + + // Current level's header size + static int localHeaderSize(PacketType type); + // Cumulated size of all the headers + static int totalHeaderSize(PacketType type, bool isPartOfMessage = false); + // The maximum payload size this packet can use to fit in MTU + static int maxPayloadSize(PacketType type, bool isPartOfMessage = false); + + static PacketType typeInHeader(const udt::Packet& packet); + static PacketVersion versionInHeader(const udt::Packet& packet); + + static QUuid sourceIDInHeader(const udt::Packet& packet); + static QByteArray verificationHashInHeader(const udt::Packet& packet); + static QByteArray hashForPacketAndSecret(const udt::Packet& packet, const QUuid& connectionSecret); + + PacketType getType() const { return _type; } + void setType(PacketType type); + + PacketVersion getVersion() const { return _version; } const QUuid& getSourceID() const { return _sourceID; } - const QByteArray& getVerificationHash() const { return _verificationHash; } - void writeSourceID(const QUuid& sourceID); - void writeVerificationHash(const QByteArray& verificationHash); - - QByteArray payloadHashWithConnectionUUID(const QUuid& connectionUUID) const; + void writeSourceID(const QUuid& sourceID) const; + void writeVerificationHashGivenSecret(const QUuid& connectionSecret) const; protected: - void adjustPayloadStartAndCapacity(); - - NLPacket(PacketType::Value type); - NLPacket(PacketType::Value type, qint64 size); + NLPacket(PacketType type, qint64 size = -1, bool forceReliable = false, bool isPartOfMessage = false); NLPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); + NLPacket(const NLPacket& other); + NLPacket(NLPacket&& other); + NLPacket(Packet&& other); + + NLPacket& operator=(const NLPacket& other); + NLPacket& operator=(NLPacket&& other); + + // Header writers + void writeTypeAndVersion(); + // Header readers, used to set member variables after getting a packet from the network + void readType(); + void readVersion(); void readSourceID(); - void readVerificationHash(); - - QUuid _sourceID; - QByteArray _verificationHash; + + PacketType _type; + PacketVersion _version; + mutable QUuid _sourceID; }; #endif // hifi_NLPacket_h diff --git a/libraries/networking/src/NLPacketList.cpp b/libraries/networking/src/NLPacketList.cpp index 8c794cf5f8..6dfd627271 100644 --- a/libraries/networking/src/NLPacketList.cpp +++ b/libraries/networking/src/NLPacketList.cpp @@ -11,15 +11,27 @@ #include "NLPacketList.h" -#include "NLPacket.h" +#include "udt/Packet.h" -NLPacketList::NLPacketList(PacketType::Value packetType, QByteArray extendedHeader) : - PacketList(packetType, extendedHeader) +NLPacketList::NLPacketList(PacketType packetType, QByteArray extendedHeader, bool isReliable, bool isOrdered) : + PacketList(packetType, extendedHeader, isReliable, isOrdered) { - } -std::unique_ptr NLPacketList::createPacket() { - return NLPacket::create(getType()); +NLPacketList::NLPacketList(PacketList&& other) : PacketList(other.getType(), other.getExtendedHeader(), other.isReliable(), other.isOrdered()) { + // Update _packets + for (auto& packet : other._packets) { + auto nlPacket = NLPacket::fromBase(std::move(packet)); + _packets.push_back(std::move(nlPacket)); + } + + if (_packets.size() > 0) { + auto nlPacket = static_cast(_packets.front().get()); + _sourceID = nlPacket->getSourceID(); + _packetType = nlPacket->getType(); + } } +std::unique_ptr NLPacketList::createPacket() { + return NLPacket::create(getType(), -1, isReliable(), isOrdered()); +} diff --git a/libraries/networking/src/NLPacketList.h b/libraries/networking/src/NLPacketList.h index 28fbde9112..5391e49488 100644 --- a/libraries/networking/src/NLPacketList.h +++ b/libraries/networking/src/NLPacketList.h @@ -14,15 +14,22 @@ #include "udt/PacketList.h" -class NLPacketList : public PacketList { +#include "NLPacket.h" + +class NLPacketList : public udt::PacketList { public: - NLPacketList(PacketType::Value packetType, QByteArray extendedHeader = QByteArray()); + NLPacketList(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false); + NLPacketList(PacketList&& packetList); + + const QUuid& getSourceID() const { return _sourceID; } private: NLPacketList(const NLPacketList& other) = delete; NLPacketList& operator=(const NLPacketList& other) = delete; - - virtual std::unique_ptr createPacket(); + + virtual std::unique_ptr createPacket(); + + QUuid _sourceID; }; #endif // hifi_PacketList_h diff --git a/libraries/networking/src/NetworkPacket.cpp b/libraries/networking/src/NetworkPacket.cpp deleted file mode 100644 index e99ef1ab8b..0000000000 --- a/libraries/networking/src/NetworkPacket.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// -// NetworkPacket.cpp -// libraries/networking/src -// -// Created by Brad Hefta-Gaub on 8/9/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include - -#include "SharedUtil.h" -#include "NetworkLogging.h" - -#include "NetworkPacket.h" - -void NetworkPacket::copyContents(const SharedNodePointer& node, const QByteArray& packet) { - if (packet.size() && packet.size() <= MAX_PACKET_SIZE) { - _node = node; - _byteArray = packet; - } else { - qCDebug(networking, ">>> NetworkPacket::copyContents() unexpected length = %d", packet.size()); - } -} - -NetworkPacket::NetworkPacket(const NetworkPacket& packet) { - copyContents(packet.getNode(), packet.getByteArray()); -} - -NetworkPacket::NetworkPacket(const SharedNodePointer& node, const QByteArray& packet) { - copyContents(node, packet); -}; - -// copy assignment -NetworkPacket& NetworkPacket::operator=(NetworkPacket const& other) { - copyContents(other.getNode(), other.getByteArray()); - return *this; -} diff --git a/libraries/networking/src/NetworkPacket.h b/libraries/networking/src/NetworkPacket.h deleted file mode 100644 index a7e5a6b3cd..0000000000 --- a/libraries/networking/src/NetworkPacket.h +++ /dev/null @@ -1,37 +0,0 @@ -// -// NetworkPacket.h -// libraries/networking/src -// -// Created by Brad Hefta-Gaub on 8/9/13. -// Copyright 2013 High Fidelity, Inc. -// -// A really simple class that stores a network packet between being received and being processed -// -// 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_NetworkPacket_h -#define hifi_NetworkPacket_h - -#include "NodeList.h" - -/// Storage of not-yet processed inbound, or not yet sent outbound generic UDP network packet -class NetworkPacket { -public: - NetworkPacket() { } - NetworkPacket(const NetworkPacket& packet); // copy constructor - NetworkPacket& operator= (const NetworkPacket& other); // copy assignment - NetworkPacket(const SharedNodePointer& node, const QByteArray& byteArray); - - const SharedNodePointer& getNode() const { return _node; } - const QByteArray& getByteArray() const { return _byteArray; } - -private: - void copyContents(const SharedNodePointer& node, const QByteArray& byteArray); - - SharedNodePointer _node; - QByteArray _byteArray; -}; - -#endif // hifi_NetworkPacket_h diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 398d4106c1..b17656aea0 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -18,9 +18,9 @@ #include #include +#include "BandwidthRecorder.h" #include "NetworkLogging.h" -#include "BandwidthRecorder.h" NetworkPeer::NetworkPeer(QObject* parent) : QObject(parent), @@ -32,7 +32,7 @@ NetworkPeer::NetworkPeer(QObject* parent) : _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _connectionAttempts(0) { - _lastHeardMicrostamp.store(usecTimestampNow()); + _lastHeardMicrostamp = usecTimestampNow(); } NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent) : @@ -45,7 +45,7 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _connectionAttempts(0) { - _lastHeardMicrostamp.store(usecTimestampNow()); + _lastHeardMicrostamp = usecTimestampNow(); } void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { @@ -54,12 +54,16 @@ void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { // if the active socket was the public socket then reset it to NULL _activeSocket = NULL; } + + bool wasOldSocketNull = _publicSocket.isNull(); - if (!_publicSocket.isNull()) { + auto temp = _publicSocket.objectName(); + _publicSocket = publicSocket; + _publicSocket.setObjectName(temp); + + if (!wasOldSocketNull) { qCDebug(networking) << "Public socket change for node" << *this; } - - _publicSocket = publicSocket; } } @@ -69,12 +73,16 @@ void NetworkPeer::setLocalSocket(const HifiSockAddr& localSocket) { // if the active socket was the local socket then reset it to NULL _activeSocket = NULL; } + + bool wasOldSocketNull = _localSocket.isNull(); + + auto temp = _localSocket.objectName(); + _localSocket = localSocket; + _localSocket.setObjectName(temp); - if (!_localSocket.isNull()) { + if (!wasOldSocketNull) { qCDebug(networking) << "Local socket change for node" << *this; } - - _localSocket = localSocket; } } @@ -84,12 +92,16 @@ void NetworkPeer::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { // if the active socket was the symmetric socket then reset it to NULL _activeSocket = NULL; } - - if (!_symmetricSocket.isNull()) { + + bool wasOldSocketNull = _symmetricSocket.isNull(); + + auto temp = _symmetricSocket.objectName(); + _symmetricSocket = symmetricSocket; + _symmetricSocket.setObjectName(temp); + + if (!wasOldSocketNull) { qCDebug(networking) << "Symmetric socket change for node" << *this; } - - _symmetricSocket = symmetricSocket; } } diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 1e03677587..8446586121 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -61,8 +61,8 @@ public: quint64 getWakeTimestamp() const { return _wakeTimestamp; } void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } - quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp.load(); } - void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp.store(lastHeardMicrostamp); } + quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } + void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } QByteArray toByteArray() const; diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 667e975398..5fea670dd0 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -32,6 +32,7 @@ void NodeType::init() { TypeNameHash.insert(NodeType::Agent, "Agent"); TypeNameHash.insert(NodeType::AudioMixer, "Audio Mixer"); TypeNameHash.insert(NodeType::AvatarMixer, "Avatar Mixer"); + TypeNameHash.insert(NodeType::AssetServer, "Asset Server"); TypeNameHash.insert(NodeType::Unassigned, "Unassigned"); } @@ -55,26 +56,28 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _canAdjustLocks(canAdjustLocks), _canRez(canRez) { - + // Update socket's object name + setType(_type); } Node::~Node() { delete _linkedData; } +void Node::setType(char type) { + _type = type; + + auto typeString = NodeType::getNodeTypeName(type); + _publicSocket.setObjectName(typeString); + _localSocket.setObjectName(typeString); + _symmetricSocket.setObjectName(typeString); +} + void Node::updateClockSkewUsec(int clockSkewSample) { _clockSkewMovingPercentile.updatePercentile((float)clockSkewSample); _clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile(); } -PacketSequenceNumber Node::getLastSequenceNumberForPacketType(PacketType::Value packetType) const { - auto typeMatch = _lastSequenceNumbers.find(packetType); - if (typeMatch != _lastSequenceNumbers.end()) { - return typeMatch->second; - } else { - return DEFAULT_SEQUENCE_NUMBER; - } -} QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._type; @@ -98,7 +101,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) { return in; } -QDebug operator<<(QDebug debug, const Node &node) { +QDebug operator<<(QDebug debug, const Node& node) { debug.nospace() << NodeType::getNodeTypeName(node.getType()); if (node.getType() == NodeType::Unassigned) { debug.nospace() << " (1)"; diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 0b5a985b35..573569f92b 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -24,7 +24,6 @@ #include "NetworkPeer.h" #include "NodeData.h" #include "NodeType.h" -#include "udt/PacketHeaders.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" @@ -41,7 +40,7 @@ public: bool operator!=(const Node& otherNode) const { return !(*this == otherNode); } char getType() const { return _type; } - void setType(char type) { _type = type; } + void setType(char type); const QUuid& getConnectionSecret() const { return _connectionSecret; } void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } @@ -65,10 +64,6 @@ public: void setCanRez(bool canRez) { _canRez = canRez; } bool getCanRez() { return _canRez; } - void setLastSequenceNumberForPacketType(PacketSequenceNumber sequenceNumber, PacketType::Value packetType) - { _lastSequenceNumbers[packetType] = sequenceNumber; } - PacketSequenceNumber getLastSequenceNumberForPacketType(PacketType::Value packetType) const; - friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -88,13 +83,11 @@ private: MovingPercentile _clockSkewMovingPercentile; bool _canAdjustLocks; bool _canRez; - - PacketTypeSequenceMap _lastSequenceNumbers; }; typedef QSharedPointer SharedNodePointer; Q_DECLARE_METATYPE(SharedNodePointer) -QDebug operator<<(QDebug debug, const Node &message); +QDebug operator<<(QDebug debug, const Node& node); #endif // hifi_Node_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 6616707579..cf202fa83a 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "NodeList.h" + #include #include #include @@ -19,17 +21,18 @@ #include #include +#include #include "AccountManager.h" #include "AddressManager.h" #include "Assignment.h" #include "HifiSockAddr.h" #include "JSONBreakableMarshal.h" -#include "NodeList.h" + +#include "NetworkLogging.h" #include "udt/PacketHeaders.h" #include "SharedUtil.h" -#include "UUID.h" -#include "NetworkLogging.h" + NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : LimitedNodeList(socketListenPort, dtlsListenPort), @@ -96,6 +99,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode"); packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket"); + packetReceiver.registerMessageListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket"); packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket"); @@ -232,7 +236,7 @@ void NodeList::sendDomainServerCheckIn() { } else if (!_domainHandler.getIP().isNull()) { bool isUsingDTLS = false; - PacketType::Value domainPacketType = !_domainHandler.isConnected() + PacketType domainPacketType = !_domainHandler.isConnected() ? PacketType::DomainConnectRequest : PacketType::DomainListRequest; if (!_domainHandler.isConnected()) { @@ -253,6 +257,7 @@ void NodeList::sendDomainServerCheckIn() { } auto domainPacket = NLPacket::create(domainPacketType); + QDataStream packetStream(domainPacket.get()); if (domainPacketType == PacketType::DomainConnectRequest) { @@ -542,7 +547,7 @@ void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { void NodeList::sendAssignment(Assignment& assignment) { - PacketType::Value assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand + PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand ? PacketType::CreateAssignment : PacketType::RequestAssignment; @@ -550,8 +555,7 @@ void NodeList::sendAssignment(Assignment& assignment) { QDataStream packetStream(assignmentPacket.get()); packetStream << assignment; - - // TODO: should this be a non sourced packet? + sendPacket(std::move(assignmentPacket), _assignmentServerSocket); } diff --git a/libraries/networking/src/NodeType.h b/libraries/networking/src/NodeType.h index 4427b87158..e680f218db 100644 --- a/libraries/networking/src/NodeType.h +++ b/libraries/networking/src/NodeType.h @@ -19,10 +19,10 @@ typedef quint8 NodeType_t; namespace NodeType { const NodeType_t DomainServer = 'D'; const NodeType_t EntityServer = 'o'; // was ModelServer - const NodeType_t EnvironmentServer = 'E'; const NodeType_t Agent = 'I'; const NodeType_t AudioMixer = 'M'; const NodeType_t AvatarMixer = 'W'; + const NodeType_t AssetServer = 'A'; const NodeType_t Unassigned = 1; void init(); diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index b1a77d4c39..fb1e2d896f 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -12,85 +12,113 @@ #include "PacketReceiver.h" +#include + #include "DependencyManager.h" #include "NetworkLogging.h" #include "NodeList.h" #include "SharedUtil.h" -PacketReceiver::PacketReceiver(QObject* parent) : - QObject(parent), - _packetListenerMap() -{ +Q_DECLARE_METATYPE(QSharedPointer); +PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) { qRegisterMetaType>(); + qRegisterMetaType>(); } -bool PacketReceiver::registerListenerForTypes(const QSet& types, QObject* listener, const char* slot) { - QSet nonSourcedTypes; - QSet sourcedTypes; - - foreach(PacketType::Value type, types) { - if (NON_SOURCED_PACKETS.contains(type)) { - nonSourcedTypes << type; - } else { - sourcedTypes << type; - } - } - - Q_ASSERT(listener); - - if (nonSourcedTypes.size() > 0) { - QMetaMethod nonSourcedMethod = matchingMethodForListener(*nonSourcedTypes.begin(), listener, slot); - if (nonSourcedMethod.isValid()) { - foreach(PacketType::Value type, nonSourcedTypes) { - registerVerifiedListener(type, listener, nonSourcedMethod); - } - } else { - return false; - } - } - - if (sourcedTypes.size() > 0) { - QMetaMethod sourcedMethod = matchingMethodForListener(*sourcedTypes.begin(), listener, slot); - if (sourcedMethod.isValid()) { - foreach(PacketType::Value type, sourcedTypes) { - registerVerifiedListener(type, listener, sourcedMethod); - } - } else { +bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) { + Q_ASSERT_X(!types.empty(), "PacketReceiver::registerListenerForTypes", "No types to register"); + Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No object to register"); + Q_ASSERT_X(slot, "PacketReceiver::registerListenerForTypes", "No slot to register"); + + // Partition types based on whether they are sourced or not (non sourced in front) + auto middle = std::partition(std::begin(types), std::end(types), [](PacketType type) { + return NON_SOURCED_PACKETS.contains(type); + }); + + QMetaMethod nonSourcedMethod, sourcedMethod; + + // Check we have a valid method for non sourced types if any + if (middle != std::begin(types)) { + nonSourcedMethod = matchingMethodForListener(*std::begin(types), listener, slot); + if (!nonSourcedMethod.isValid()) { return false; } } + // Check we have a valid method for sourced types if any + if (middle != std::end(types)) { + sourcedMethod = matchingMethodForListener(*middle, listener, slot); + if (!sourcedMethod.isValid()) { + return false; + } + } + + // Register non sourced types + std::for_each(std::begin(types), middle, [this, &listener, &nonSourcedMethod](PacketType type) { + registerVerifiedListener(type, listener, nonSourcedMethod); + }); + + // Register sourced types + std::for_each(middle, std::end(types), [this, &listener, &sourcedMethod](PacketType type) { + registerVerifiedListener(type, listener, sourcedMethod); + }); + return true; } -void PacketReceiver::registerDirectListener(PacketType::Value type, QObject* listener, const char* slot) { +void PacketReceiver::registerDirectListener(PacketType type, QObject* listener, const char* slot) { + Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No object to register"); + Q_ASSERT_X(slot, "PacketReceiver::registerDirectListener", "No slot to register"); + bool success = registerListener(type, listener, slot); if (success) { - _directConnectSetMutex.lock(); + QMutexLocker locker(&_directConnectSetMutex); // if we successfully registered, add this object to the set of objects that are directly connected _directlyConnectedObjects.insert(listener); - - _directConnectSetMutex.unlock(); } } -void PacketReceiver::registerDirectListenerForTypes(const QSet& types, +void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) { + Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No object to register"); + Q_ASSERT_X(slot, "PacketReceiver::registerDirectListenerForTypes", "No slot to register"); + // just call register listener for types to start - bool success = registerListenerForTypes(types, listener, slot); + bool success = registerListenerForTypes(std::move(types), listener, slot); if (success) { - _directConnectSetMutex.lock(); + QMutexLocker locker(&_directConnectSetMutex); // if we successfully registered, add this object to the set of objects that are directly connected _directlyConnectedObjects.insert(listener); - - _directConnectSetMutex.unlock(); } } -bool PacketReceiver::registerListener(PacketType::Value type, QObject* listener, const char* slot) { - Q_ASSERT(listener); +bool PacketReceiver::registerMessageListener(PacketType type, QObject* listener, const char* slot) { + Q_ASSERT_X(listener, "PacketReceiver::registerMessageListener", "No object to register"); + Q_ASSERT_X(slot, "PacketReceiver::registerMessageListener", "No slot to register"); + + QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot); + + if (matchingMethod.isValid()) { + QMutexLocker locker(&_packetListenerLock); + + if (_packetListListenerMap.contains(type)) { + qCWarning(networking) << "Registering a packet listener for packet type" << type + << "that will remove a previously registered listener"; + } + + // add the mapping + _packetListListenerMap[type] = ObjectMethodPair(QPointer(listener), matchingMethod); + return true; + } else { + return false; + } +} + +bool PacketReceiver::registerListener(PacketType type, QObject* listener, const char* slot) { + Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No object to register"); + Q_ASSERT_X(slot, "PacketReceiver::registerListener", "No slot to register"); QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot); @@ -102,24 +130,34 @@ bool PacketReceiver::registerListener(PacketType::Value type, QObject* listener, } } -QMetaMethod PacketReceiver::matchingMethodForListener(PacketType::Value type, QObject* object, const char* slot) const { - Q_ASSERT(object); +QMetaMethod PacketReceiver::matchingMethodForListener(PacketType type, QObject* object, const char* slot) const { + Q_ASSERT_X(object, "PacketReceiver::matchingMethodForListener", "No object to call"); + Q_ASSERT_X(slot, "PacketReceiver::matchingMethodForListener", "No slot to call"); // normalize the slot with the expected parameters + + static const QString SIGNATURE_TEMPLATE("%1(%2)"); + static const QString NON_SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer"; + static const QString NON_SOURCED_PACKETLIST_LISTENER_PARAMETERS = "QSharedPointer"; - const QString NON_SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer"; - - QSet possibleSignatures { QString("%1(%2)").arg(slot).arg(NON_SOURCED_PACKET_LISTENER_PARAMETERS) }; + QSet possibleSignatures { + SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_PACKET_LISTENER_PARAMETERS), + SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_PACKETLIST_LISTENER_PARAMETERS) + }; if (!NON_SOURCED_PACKETS.contains(type)) { static const QString SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer,QSharedPointer"; static const QString TYPEDEF_SOURCED_PACKET_LISTENER_PARAMETERS = "QSharedPointer,SharedNodePointer"; + static const QString SOURCED_PACKETLIST_LISTENER_PARAMETERS = "QSharedPointer,QSharedPointer"; + static const QString TYPEDEF_SOURCED_PACKETLIST_LISTENER_PARAMETERS = "QSharedPointer,SharedNodePointer"; // a sourced packet must take the shared pointer to the packet but optionally could include // a shared pointer to the node - possibleSignatures << QString("%1(%2)").arg(slot).arg(TYPEDEF_SOURCED_PACKET_LISTENER_PARAMETERS); - possibleSignatures << QString("%1(%2)").arg(slot).arg(SOURCED_PACKET_LISTENER_PARAMETERS); + possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_PACKET_LISTENER_PARAMETERS); + possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_PACKET_LISTENER_PARAMETERS); + possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_PACKETLIST_LISTENER_PARAMETERS); + possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_PACKETLIST_LISTENER_PARAMETERS); } int methodIndex = -1; @@ -137,10 +175,9 @@ QMetaMethod PacketReceiver::matchingMethodForListener(PacketType::Value type, QO } if (methodIndex < 0) { - qDebug() << "PacketReceiver::registerListener expected a slot with one of the following signatures:" + qCDebug(networking) << "PacketReceiver::registerListener expected a slot with one of the following signatures:" << possibleSignatures.toList() << "- but such a slot was not found." - << "Could not complete listener registration for type" - << type << "-" << nameForPacketType(type); + << "Could not complete listener registration for type" << type; } Q_ASSERT(methodIndex >= 0); @@ -155,228 +192,285 @@ QMetaMethod PacketReceiver::matchingMethodForListener(PacketType::Value type, QO } } -void PacketReceiver::registerVerifiedListener(PacketType::Value type, QObject* object, const QMetaMethod& slot) { - _packetListenerLock.lock(); +void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot) { + Q_ASSERT_X(object, "PacketReceiver::registerVerifiedListener", "No object to register"); + QMutexLocker locker(&_packetListenerLock); if (_packetListenerMap.contains(type)) { - qDebug() << "Warning: Registering a packet listener for packet type" << type - << "(" << qPrintable(nameForPacketType(type)) << ")" + qCWarning(networking) << "Registering a packet listener for packet type" << type << "that will remove a previously registered listener"; } - + // add the mapping _packetListenerMap[type] = ObjectMethodPair(QPointer(object), slot); - - _packetListenerLock.unlock(); - } void PacketReceiver::unregisterListener(QObject* listener) { - _packetListenerLock.lock(); - - auto it = _packetListenerMap.begin(); - - while (it != _packetListenerMap.end()) { - if (it.value().first == listener) { - // this listener matches - erase it - it = _packetListenerMap.erase(it); - } else { - ++it; - } - } - - _packetListenerLock.unlock(); + Q_ASSERT_X(listener, "PacketReceiver::unregisterListener", "No listener to unregister"); - _directConnectSetMutex.lock(); - _directlyConnectedObjects.remove(listener); - _directConnectSetMutex.unlock(); -} - -bool PacketReceiver::packetVersionMatch(const NLPacket& packet) { - - if (packet.getVersion() != versionForPacketType(packet.getType()) - && packet.getType() != PacketType::StunResponse) { - - static QMultiHash sourcedVersionDebugSuppressMap; - static QMultiHash versionDebugSuppressMap; - - bool hasBeenOutput = false; - QString senderString; + { + QMutexLocker packetListenerLocker(&_packetListenerLock); - if (NON_SOURCED_PACKETS.contains(packet.getType())) { - const HifiSockAddr& senderSockAddr = packet.getSenderSockAddr(); - hasBeenOutput = versionDebugSuppressMap.contains(senderSockAddr, packet.getType()); - - if (!hasBeenOutput) { - versionDebugSuppressMap.insert(senderSockAddr, packet.getType()); - senderString = QString("%1:%2").arg(senderSockAddr.getAddress().toString()).arg(senderSockAddr.getPort()); - } - } else { - hasBeenOutput = sourcedVersionDebugSuppressMap.contains(packet.getSourceID(), packet.getType()); - - if (!hasBeenOutput) { - sourcedVersionDebugSuppressMap.insert(packet.getSourceID(), packet.getType()); - senderString = uuidStringWithoutCurlyBraces(packet.getSourceID().toString()); + // TODO: replace the two while loops below with a replace_if on the vector (once we move to Message everywhere) + + // clear any registrations for this listener in _packetListenerMap + auto it = _packetListenerMap.begin(); + + while (it != _packetListenerMap.end()) { + if (it.value().first == listener) { + it = _packetListenerMap.erase(it); + } else { + ++it; } } - - if (!hasBeenOutput) { - qCDebug(networking) << "Packet version mismatch on" << packet.getType() << "- Sender" - << senderString << "sent" << qPrintable(QString::number(packet.getVersion())) << "but" - << qPrintable(QString::number(versionForPacketType(packet.getType()))) << "expected."; - - emit packetVersionMismatch(packet.getType()); + + // clear any registrations for this listener in _packetListListener + auto listIt = _packetListListenerMap.begin(); + + while (listIt != _packetListListenerMap.end()) { + if (listIt.value().first == listener) { + listIt = _packetListListenerMap.erase(listIt); + } else { + ++listIt; + } } - - return false; - } else { - return true; } + + QMutexLocker directConnectSetLocker(&_directConnectSetMutex); + _directlyConnectedObjects.remove(listener); } -void PacketReceiver::processDatagrams() { - //PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - //"PacketReceiver::processDatagrams()"); +void PacketReceiver::handleVerifiedPacketList(std::unique_ptr packetList) { + // if we're supposed to drop this packet then break out here + if (_shouldDropPackets) { + return; + } + + // setup an NLPacketList from the PacketList we were passed + auto nlPacketList = new NLPacketList(std::move(*packetList)); auto nodeList = DependencyManager::get(); - - while (nodeList && nodeList->getNodeSocket().hasPendingDatagrams()) { - // setup a buffer to read the packet into - int packetSizeWithHeader = nodeList->getNodeSocket().pendingDatagramSize(); - auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); - - // if we're supposed to drop this packet then break out here - if (_shouldDropPackets) { - break; - } - - // setup a HifiSockAddr to read into - HifiSockAddr senderSockAddr; - - // pull the datagram - nodeList->getNodeSocket().readDatagram(buffer.get(), packetSizeWithHeader, - senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - - // setup an NLPacket from the data we just read - auto packet = NLPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + + _inPacketCount += nlPacketList->getNumPackets(); + _inByteCount += nlPacketList->getDataSize(); + + SharedNodePointer matchingNode; + + if (!nlPacketList->getSourceID().isNull()) { + matchingNode = nodeList->nodeWithUUID(nlPacketList->getSourceID()); + } + + QMutexLocker packetListenerLocker(&_packetListenerLock); + + bool listenerIsDead = false; + + auto it = _packetListListenerMap.find(nlPacketList->getType()); + + if (it != _packetListListenerMap.end() && it->second.isValid()) { - _inPacketCount++; - _inByteCount += packetSizeWithHeader; - - if (packetVersionMatch(*packet)) { + auto listener = it.value(); + + if (listener.first) { - SharedNodePointer matchingNode; - if (nodeList->packetSourceAndHashMatch(*packet, matchingNode)) { - - if (matchingNode) { - // No matter if this packet is handled or not, we update the timestamp for the last time we heard - // from this sending node - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - } - - _packetListenerLock.lock(); + bool success = false; + + Qt::ConnectionType connectionType; + // check if this is a directly connected listener + { + QMutexLocker directConnectLocker(&_directConnectSetMutex); + + connectionType = _directlyConnectedObjects.contains(listener.first) ? Qt::DirectConnection : Qt::AutoConnection; + } + + PacketType packetType = nlPacketList->getType(); + + if (matchingNode) { + emit dataReceived(matchingNode->getType(), nlPacketList->getDataSize()); + QMetaMethod metaMethod = listener.second; - bool listenerIsDead = false; - - auto it = _packetListenerMap.find(packet->getType()); - - if (it != _packetListenerMap.end() && it->second.isValid()) { - - auto listener = it.value(); - - if (listener.first) { - - bool success = false; + static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); + static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); + + // one final check on the QPointer before we go to invoke + if (listener.first) { + if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { + success = metaMethod.invoke(listener.first, + connectionType, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacketList)), + Q_ARG(SharedNodePointer, matchingNode)); - // check if this is a directly connected listener - _directConnectSetMutex.lock(); + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { + success = metaMethod.invoke(listener.first, + connectionType, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacketList)), + Q_ARG(QSharedPointer, matchingNode)); - Qt::ConnectionType connectionType = - _directlyConnectedObjects.contains(listener.first) ? Qt::DirectConnection : Qt::AutoConnection; - - _directConnectSetMutex.unlock(); - - PacketType::Value packetType = packet->getType(); - - if (matchingNode) { - // if this was a sequence numbered packet we should store the last seq number for - // a packet of this type for this node - if (SEQUENCE_NUMBERED_PACKETS.contains(packet->getType())) { - matchingNode->setLastSequenceNumberForPacketType(packet->readSequenceNumber(), packet->getType()); - } - - emit dataReceived(matchingNode->getType(), packet->getDataSize()); - QMetaMethod metaMethod = listener.second; - - static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); - static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); - - // one final check on the QPointer before we go to invoke - if (listener.first) { - if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.first, - connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(packet.release())), - Q_ARG(SharedNodePointer, matchingNode)); - - } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.first, - connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(packet.release())), - Q_ARG(QSharedPointer, matchingNode)); - - } else { - success = metaMethod.invoke(listener.first, - connectionType, - Q_ARG(QSharedPointer, - QSharedPointer(packet.release()))); - } - } else { - listenerIsDead = true; - } - - } else { - emit dataReceived(NodeType::Unassigned, packet->getDataSize()); - - success = listener.second.invoke(listener.first, - Q_ARG(QSharedPointer, QSharedPointer(packet.release()))); - } - - if (!success) { - qDebug().nospace() << "Error delivering packet " << packetType - << " (" << qPrintable(nameForPacketType(packetType)) << ") to listener " - << listener.first << "::" << qPrintable(listener.second.methodSignature()); - } - } else { - listenerIsDead = true; + success = metaMethod.invoke(listener.first, + connectionType, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacketList))); } - - if (listenerIsDead) { - qDebug().nospace() << "Listener for packet" << packet->getType() - << " (" << qPrintable(nameForPacketType(packet->getType())) << ")" - << " has been destroyed. Removing from listener map."; - it = _packetListenerMap.erase(it); - - // if it exists, remove the listener from _directlyConnectedObjects - _directConnectSetMutex.lock(); - _directlyConnectedObjects.remove(listener.first); - _directConnectSetMutex.unlock(); - } - } else { - if (it == _packetListenerMap.end()) { - qWarning() << "No listener found for packet type " << nameForPacketType(packet->getType()); - - // insert a dummy listener so we don't print this again - _packetListenerMap.insert(packet->getType(), { nullptr, QMetaMethod() }); - } + listenerIsDead = true; } - - _packetListenerLock.unlock(); + } else { + emit dataReceived(NodeType::Unassigned, nlPacketList->getDataSize()); + + // one final check on the QPointer before we invoke + if (listener.first) { + success = listener.second.invoke(listener.first, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacketList))); + } else { + listenerIsDead = true; + } + + } + + if (!success) { + qCDebug(networking).nospace() << "Error delivering packet " << packetType << " to listener " + << listener.first << "::" << qPrintable(listener.second.methodSignature()); + } + + } else { + listenerIsDead = true; + } + + if (listenerIsDead) { + qCDebug(networking).nospace() << "Listener for packet " << nlPacketList->getType() + << " has been destroyed. Removing from listener map."; + it = _packetListListenerMap.erase(it); + + // if it exists, remove the listener from _directlyConnectedObjects + { + QMutexLocker directConnectLocker(&_directConnectSetMutex); + _directlyConnectedObjects.remove(listener.first); } } + + } else if (it == _packetListListenerMap.end()) { + qCWarning(networking) << "No listener found for packet type" << nlPacketList->getType(); + + // insert a dummy listener so we don't print this again + _packetListListenerMap.insert(nlPacketList->getType(), { nullptr, QMetaMethod() }); + } +} + +void PacketReceiver::handleVerifiedPacket(std::unique_ptr packet) { + + // if we're supposed to drop this packet then break out here + if (_shouldDropPackets) { + return; + } + + auto nodeList = DependencyManager::get(); + + // setup an NLPacket from the packet we were passed + auto nlPacket = NLPacket::fromBase(std::move(packet)); + + _inPacketCount++; + _inByteCount += nlPacket->getDataSize(); + + SharedNodePointer matchingNode; + + if (!nlPacket->getSourceID().isNull()) { + matchingNode = nodeList->nodeWithUUID(nlPacket->getSourceID()); + } + + QMutexLocker packetListenerLocker(&_packetListenerLock); + + bool listenerIsDead = false; + + auto it = _packetListenerMap.find(nlPacket->getType()); + + if (it != _packetListenerMap.end() && it->second.isValid()) { + + auto listener = it.value(); + + if (listener.first) { + + bool success = false; + + // check if this is a directly connected listener + QMutexLocker directConnectSetLocker(&_directConnectSetMutex); + Qt::ConnectionType connectionType = + _directlyConnectedObjects.contains(listener.first) ? Qt::DirectConnection : Qt::AutoConnection; + directConnectSetLocker.unlock(); + + PacketType packetType = nlPacket->getType(); + + if (matchingNode) { + emit dataReceived(matchingNode->getType(), nlPacket->getDataSize()); + QMetaMethod metaMethod = listener.second; + + static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); + static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); + + // one final check on the QPointer before we go to invoke + if (listener.first) { + if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { + success = metaMethod.invoke(listener.first, + connectionType, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacket.release())), + Q_ARG(SharedNodePointer, matchingNode)); + + } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { + success = metaMethod.invoke(listener.first, + connectionType, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacket.release())), + Q_ARG(QSharedPointer, matchingNode)); + + } else { + success = metaMethod.invoke(listener.first, + connectionType, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacket.release()))); + } + } else { + listenerIsDead = true; + } + } else { + emit dataReceived(NodeType::Unassigned, nlPacket->getDataSize()); + + // one final check on the QPointer before we invoke + if (listener.first) { + success = listener.second.invoke(listener.first, + Q_ARG(QSharedPointer, + QSharedPointer(nlPacket.release()))); + } else { + listenerIsDead = true; + } + + } + + if (!success) { + qCDebug(networking).nospace() << "Error delivering packet " << packetType << " to listener " + << listener.first << "::" << qPrintable(listener.second.methodSignature()); + } + + } else { + listenerIsDead = true; + } + + if (listenerIsDead) { + qCDebug(networking).nospace() << "Listener for packet " << nlPacket->getType() + << " has been destroyed. Removing from listener map."; + it = _packetListenerMap.erase(it); + + // if it exists, remove the listener from _directlyConnectedObjects + QMutexLocker locker(&_directConnectSetMutex); + _directlyConnectedObjects.remove(listener.first); + } + + } else if (it == _packetListenerMap.end()) { + qCWarning(networking) << "No listener found for packet type" << nlPacket->getType(); + + // insert a dummy listener so we don't print this again + _packetListenerMap.insert(nlPacket->getType(), { nullptr, QMetaMethod() }); } } diff --git a/libraries/networking/src/PacketReceiver.h b/libraries/networking/src/PacketReceiver.h index 9fdccdfddf..1c6f9e73d2 100644 --- a/libraries/networking/src/PacketReceiver.h +++ b/libraries/networking/src/PacketReceiver.h @@ -13,6 +13,8 @@ #ifndef hifi_PacketReceiver_h #define hifi_PacketReceiver_h +#include + #include #include #include @@ -21,6 +23,7 @@ #include #include "NLPacket.h" +#include "NLPacketList.h" #include "udt/PacketHeaders.h" class EntityEditPacketSender; @@ -29,6 +32,8 @@ class OctreePacketProcessor; class PacketReceiver : public QObject { Q_OBJECT public: + using PacketTypeList = std::vector; + PacketReceiver(QObject* parent = 0); PacketReceiver(const PacketReceiver&) = delete; @@ -41,32 +46,32 @@ public: void resetCounters() { _inPacketCount = 0; _inByteCount = 0; } - bool registerListenerForTypes(const QSet& types, QObject* listener, const char* slot); - bool registerListener(PacketType::Value type, QObject* listener, const char* slot); + bool registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); + bool registerMessageListener(PacketType type, QObject* listener, const char* slot); + bool registerListener(PacketType type, QObject* listener, const char* slot); void unregisterListener(QObject* listener); - -public slots: - void processDatagrams(); + + void handleVerifiedPacket(std::unique_ptr packet); + void handleVerifiedPacketList(std::unique_ptr packetList); signals: void dataReceived(quint8 channelType, int bytes); - void packetVersionMismatch(PacketType::Value type); private: // these are brutal hacks for now - ideally GenericThread / ReceivedPacketProcessor // should be changed to have a true event loop and be able to handle our QMetaMethod::invoke - void registerDirectListenerForTypes(const QSet& types, QObject* listener, const char* slot); - void registerDirectListener(PacketType::Value type, QObject* listener, const char* slot); - - bool packetVersionMatch(const NLPacket& packet); + void registerDirectListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); + void registerDirectListener(PacketType type, QObject* listener, const char* slot); - QMetaMethod matchingMethodForListener(PacketType::Value type, QObject* object, const char* slot) const; - void registerVerifiedListener(PacketType::Value type, QObject* listener, const QMetaMethod& slot); + QMetaMethod matchingMethodForListener(PacketType type, QObject* object, const char* slot) const; + void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot); using ObjectMethodPair = std::pair, QMetaMethod>; QMutex _packetListenerLock; - QHash _packetListenerMap; + // TODO: replace the two following hashes with an std::vector once we switch Packet/PacketList to Message + QHash _packetListenerMap; + QHash _packetListListenerMap; int _inPacketCount = 0; int _inByteCount = 0; bool _shouldDropPackets = false; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 75028abe93..18135cf6df 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -154,21 +154,28 @@ void ResourceCache::clearUnusedResource() { void ResourceCache::attemptRequest(Resource* resource) { auto sharedItems = DependencyManager::get(); - if (_requestLimit <= 0) { - // wait until a slot becomes available - sharedItems->_pendingRequests.append(resource); - return; + + // Disable request limiting for ATP + if (resource->getURL().scheme() != URL_SCHEME_ATP) { + if (_requestLimit <= 0) { + // wait until a slot becomes available + sharedItems->_pendingRequests.append(resource); + return; + } + + --_requestLimit; } - _requestLimit--; + sharedItems->_loadingRequests.append(resource); resource->makeRequest(); } void ResourceCache::requestCompleted(Resource* resource) { - auto sharedItems = DependencyManager::get(); sharedItems->_loadingRequests.removeOne(resource); - _requestLimit++; + if (resource->getURL().scheme() != URL_SCHEME_ATP) { + ++_requestLimit; + } // look for the highest priority pending request int highestIndex = -1; @@ -196,24 +203,22 @@ int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT; Resource::Resource(const QUrl& url, bool delayLoad) : _url(url), - _request(url) { + _activeUrl(url), + _request(nullptr) { init(); - _request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); - // start loading immediately unless instructed otherwise if (!(_startedLoading || delayLoad)) { - attemptRequest(); + QTimer::singleShot(0, this, &Resource::ensureLoading); } } Resource::~Resource() { - if (_reply) { + if (_request) { ResourceCache::requestCompleted(this); - delete _reply; - _reply = nullptr; + _request->deleteLater(); + _request = nullptr; } } @@ -259,21 +264,17 @@ float Resource::getLoadPriority() { } void Resource::refresh() { - if (_reply && !(_loaded || _failedToLoad)) { + if (_request && !(_loaded || _failedToLoad)) { return; } - if (_reply) { + if (_request) { + _request->disconnect(this); + _request->deleteLater(); + _request = nullptr; ResourceCache::requestCompleted(this); - _reply->disconnect(this); - _replyTimer->disconnect(this); - _reply->deleteLater(); - _reply = nullptr; - _replyTimer->deleteLater(); - _replyTimer = nullptr; } init(); - _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); ensureLoading(); emit onRefresh(); } @@ -303,6 +304,7 @@ void Resource::init() { _failedToLoad = false; _loaded = false; _attempts = 0; + _activeUrl = _url; if (_url.isEmpty()) { _startedLoading = _loaded = true; @@ -319,8 +321,10 @@ void Resource::attemptRequest() { void Resource::finishedLoading(bool success) { if (success) { + qDebug() << "Finished loading:" << _url; _loaded = true; } else { + qDebug() << "Failed to load:" << _url; _failedToLoad = true; } _loadPriorities.clear(); @@ -330,95 +334,83 @@ void Resource::reinsert() { _cache->_resources.insert(_url, _self); } -static const int REPLY_TIMEOUT_MS = 5000; -void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - _bytesReceived = bytesReceived; - _bytesTotal = bytesTotal; - _replyTimer->start(REPLY_TIMEOUT_MS); -} - -void Resource::handleReplyError() { - handleReplyErrorInternal(_reply->error()); -} - -void Resource::handleReplyTimeout() { - handleReplyErrorInternal(QNetworkReply::TimeoutError); -} void Resource::makeRequest() { - _reply = NetworkAccessManager::getInstance().get(_request); + Q_ASSERT(!_request); - connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); - connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); - connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished())); + _request = ResourceManager::createResourceRequest(this, _activeUrl); + + if (!_request) { + qDebug() << "Failed to get request for " << _url; + ResourceCache::requestCompleted(this); + finishedLoading(false); + return; + } + + qDebug() << "Starting request for: " << _url; + + connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress); + connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished); - _replyTimer = new QTimer(this); - connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout())); - _replyTimer->setSingleShot(true); - _replyTimer->start(REPLY_TIMEOUT_MS); _bytesReceived = _bytesTotal = 0; + + _request->send(); } -void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) { - - _reply->disconnect(this); - _replyTimer->disconnect(this); - _reply->deleteLater(); - _reply = nullptr; - _replyTimer->deleteLater(); - _replyTimer = nullptr; - ResourceCache::requestCompleted(this); - - // retry for certain types of failures - switch (error) { - case QNetworkReply::RemoteHostClosedError: - case QNetworkReply::TimeoutError: - case QNetworkReply::TemporaryNetworkFailureError: - case QNetworkReply::ProxyConnectionClosedError: - case QNetworkReply::ProxyTimeoutError: - case QNetworkReply::UnknownNetworkError: - case QNetworkReply::UnknownProxyError: - case QNetworkReply::UnknownContentError: - case QNetworkReply::ProtocolFailure: { - // retry with increasing delays - const int MAX_ATTEMPTS = 8; - const int BASE_DELAY_MS = 1000; - if (++_attempts < MAX_ATTEMPTS) { - QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest())); - qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")"; - return; - } - // fall through to final failure - } - default: - qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error; - emit failed(error); - finishedLoading(false); - break; - } +void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal) { + _bytesReceived = bytesReceived; + _bytesTotal = bytesTotal; } void Resource::handleReplyFinished() { - - bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); - qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : ""); - - _reply->disconnect(this); - _replyTimer->disconnect(this); - QNetworkReply* reply = _reply; - _reply = nullptr; - _replyTimer->deleteLater(); - _replyTimer = nullptr; + Q_ASSERT(_request); + ResourceCache::requestCompleted(this); - - finishedLoading(true); - emit loaded(*reply); - downloadFinished(reply); + + auto result = _request->getResult(); + if (result == ResourceRequest::Success) { + _data = _request->getData(); + qDebug() << "Request finished for " << _url << ", " << _activeUrl; + + finishedLoading(true); + emit loaded(_data); + downloadFinished(_data); + } else { + switch (result) { + case ResourceRequest::Result::Timeout: { + qDebug() << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal; + // Fall through to other cases + } + case ResourceRequest::Result::ServerUnavailable: { + // retry with increasing delays + const int MAX_ATTEMPTS = 8; + const int BASE_DELAY_MS = 1000; + if (_attempts++ < MAX_ATTEMPTS) { + auto waitTime = BASE_DELAY_MS * (int)pow(2.0, _attempts); + qDebug().nospace() << "Retrying to load the asset in " << waitTime + << "ms, attempt " << _attempts << " of " << MAX_ATTEMPTS; + QTimer::singleShot(waitTime, this, &Resource::attemptRequest); + break; + } + // fall through to final failure + } + default: { + qDebug() << "Error loading " << _url; + auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError + : QNetworkReply::UnknownNetworkError; + emit failed(error); + finishedLoading(false); + break; + } + } + } + + _request->disconnect(this); + _request->deleteLater(); + _request = nullptr; } - -void Resource::downloadFinished(QNetworkReply* reply) { - ; +void Resource::downloadFinished(const QByteArray& data) { } uint qHash(const QPointer& value, uint seed) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 9a88c434e1..a11ee30174 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -26,6 +26,8 @@ #include +#include "ResourceManager.h" + class QNetworkReply; class QTimer; @@ -102,7 +104,7 @@ protected: void reserveUnusedResource(qint64 resourceSize); void clearUnusedResource(); - static void attemptRequest(Resource* resource); + Q_INVOKABLE static void attemptRequest(Resource* resource); static void requestCompleted(Resource* resource); private: @@ -171,10 +173,11 @@ public: Q_INVOKABLE void allReferencesCleared(); const QUrl& getURL() const { return _url; } + const QByteArray& getData() const { return _data; } signals: /// Fired when the resource has been loaded. - void loaded(QNetworkReply& request); + void loaded(const QByteArray& request); /// Fired when resource failed to load. void failed(QNetworkReply::NetworkError error); @@ -188,8 +191,8 @@ protected slots: protected: virtual void init(); - /// Called when the download has finished. The recipient should delete the reply when done with it. - virtual void downloadFinished(QNetworkReply* reply); + /// Called when the download has finished + virtual void downloadFinished(const QByteArray& data); /// Should be called by subclasses when all the loading that will be done has been done. Q_INVOKABLE void finishedLoading(bool success); @@ -198,31 +201,29 @@ protected: virtual void reinsert(); QUrl _url; - QNetworkRequest _request; + QUrl _activeUrl; bool _startedLoading = false; bool _failedToLoad = false; bool _loaded = false; QHash, float> _loadPriorities; QWeakPointer _self; QPointer _cache; + QByteArray _data; private slots: - void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void handleReplyError(); + void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal); void handleReplyFinished(); - void handleReplyTimeout(); private: void setLRUKey(int lruKey) { _lruKey = lruKey; } void makeRequest(); - - void handleReplyErrorInternal(QNetworkReply::NetworkError error); + void retry(); friend class ResourceCache; + ResourceRequest* _request = nullptr; int _lruKey = 0; - QNetworkReply* _reply = nullptr; QTimer* _replyTimer = nullptr; qint64 _bytesReceived = 0; qint64 _bytesTotal = 0; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp new file mode 100644 index 0000000000..c721419c7e --- /dev/null +++ b/libraries/networking/src/ResourceManager.cpp @@ -0,0 +1,33 @@ +// +// ResourceManager.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 "ResourceManager.h" + +#include "AssetResourceRequest.h" +#include "FileResourceRequest.h" +#include "HTTPResourceRequest.h" + +#include + +ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { + auto scheme = url.scheme(); + if (scheme == URL_SCHEME_FILE) { + return new FileResourceRequest(parent, url); + } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { + return new HTTPResourceRequest(parent, url); + } else if (scheme == URL_SCHEME_ATP) { + return new AssetResourceRequest(parent, url); + } + + qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); + + return nullptr; +} diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h new file mode 100644 index 0000000000..3748036c8e --- /dev/null +++ b/libraries/networking/src/ResourceManager.h @@ -0,0 +1,30 @@ +// +// ResourceManager.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 +// + +#ifndef hifi_ResourceManager_h +#define hifi_ResourceManager_h + +#include + +#include "ResourceRequest.h" + +const QString URL_SCHEME_FILE = "file"; +const QString URL_SCHEME_HTTP = "http"; +const QString URL_SCHEME_HTTPS = "https"; +const QString URL_SCHEME_FTP = "ftp"; +const QString URL_SCHEME_ATP = "atp"; + +class ResourceManager { +public: + static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); +}; + +#endif diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp new file mode 100644 index 0000000000..c6880636ea --- /dev/null +++ b/libraries/networking/src/ResourceRequest.cpp @@ -0,0 +1,24 @@ +// +// ResourceRequest.cpp +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 "ResourceRequest.h" + +ResourceRequest::ResourceRequest(QObject* parent, const QUrl& url) : + QObject(parent), + _url(url) { +} + +void ResourceRequest::send() { + Q_ASSERT(_state == NotStarted); + + _state = InProgress; + doSend(); +} diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h new file mode 100644 index 0000000000..b229951a49 --- /dev/null +++ b/libraries/networking/src/ResourceRequest.h @@ -0,0 +1,65 @@ +// +// ResourceRequest.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/23 +// 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 +// + +#ifndef hifi_ResourceRequest_h +#define hifi_ResourceRequest_h + +#include +#include + +#include + +class ResourceRequest : public QObject { + Q_OBJECT +public: + ResourceRequest(QObject* parent, const QUrl& url); + + enum State { + NotStarted = 0, + InProgress, + Finished + }; + + enum Result { + Success, + Error, + Timeout, + ServerUnavailable, + AccessDenied, + InvalidURL, + NotFound + }; + + void send(); + QByteArray getData() { return _data; } + State getState() const { return _state; } + Result getResult() const { return _result; } + QUrl getUrl() const { return _url; } + bool loadedFromCache() const { return _loadedFromCache; } + + void setCacheEnabled(bool value) { _cacheEnabled = value; } + +signals: + void progress(uint64_t bytesReceived, uint64_t bytesTotal); + void finished(); + +protected: + virtual void doSend() = 0; + + QUrl _url; + State _state { NotStarted }; + Result _result; + QByteArray _data; + bool _cacheEnabled { true }; + bool _loadedFromCache { false }; +}; + +#endif diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 1c425806c9..a5df256e29 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -70,10 +70,11 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); if (shouldSendStats) { - // send a stats packet every 1 second - _statsTimer = new QTimer(); - connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); - _statsTimer->start(1000); + // start sending stats packet once we connect to the domain + connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, this, &ThreadedAssignment::startSendingStats); + + // stop sending stats if we disconnect + connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, this, &ThreadedAssignment::stopSendingStats); } } @@ -96,6 +97,21 @@ void ThreadedAssignment::sendStatsPacket() { addPacketStatsAndSendStatsPacket(statsObject); } +void ThreadedAssignment::startSendingStats() { + // send the stats packet every 1s + if (!_statsTimer) { + _statsTimer = new QTimer(); + connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); + } + + _statsTimer->start(1000); +} + +void ThreadedAssignment::stopSendingStats() { + // stop sending stats, we just disconnected from domain + _statsTimer->stop(); +} + void ThreadedAssignment::checkInWithDomainServerOrExit() { if (DependencyManager::get()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 51917cd74d..9ff3b34add 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -42,6 +42,8 @@ protected: QTimer* _statsTimer = nullptr; private slots: + void startSendingStats(); + void stopSendingStats(); void checkInWithDomainServerOrExit(); }; diff --git a/libraries/networking/src/WalletTransaction.cpp b/libraries/networking/src/WalletTransaction.cpp index 9c8226a908..0c823555fd 100644 --- a/libraries/networking/src/WalletTransaction.cpp +++ b/libraries/networking/src/WalletTransaction.cpp @@ -1,6 +1,6 @@ // // WalletTransaction.cpp -// domain-server/src +// libraries/networking/src // // Created by Stephen Birarda on 2014-05-20. // Copyright 2014 High Fidelity, Inc. @@ -15,6 +15,7 @@ #include "WalletTransaction.h" + WalletTransaction::WalletTransaction() : _uuid(), _destinationUUID(), @@ -64,4 +65,4 @@ void WalletTransaction::loadFromJson(const QJsonObject& jsonObject) { _uuid = QUuid(transactionObject.value(TRANSACTION_ID_KEY).toString()); _destinationUUID = QUuid(transactionObject.value(TRANSACTION_DESTINATION_WALLET_ID_KEY).toString()); _amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toInt(); -} \ No newline at end of file +} diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp new file mode 100644 index 0000000000..0d661ad34a --- /dev/null +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -0,0 +1,208 @@ +// +// BasePacket.cpp +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-23. +// 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 "BasePacket.h" + +using namespace udt; + +const qint64 BasePacket::PACKET_WRITE_ERROR = -1; + +int BasePacket::localHeaderSize() { + return 0; +} +int BasePacket::totalHeaderSize() { + return 0; +} +int BasePacket::maxPayloadSize() { + return MAX_PACKET_SIZE; +} + +std::unique_ptr BasePacket::create(qint64 size) { + auto packet = std::unique_ptr(new BasePacket(size)); + + packet->open(QIODevice::ReadWrite); + + return packet; +} + +std::unique_ptr BasePacket::fromReceivedPacket(std::unique_ptr data, + qint64 size, const HifiSockAddr& senderSockAddr) { + // Fail with invalid size + Q_ASSERT(size >= 0); + + // allocate memory + auto packet = std::unique_ptr(new BasePacket(std::move(data), size, senderSockAddr)); + + packet->open(QIODevice::ReadOnly); + + return packet; +} + +BasePacket::BasePacket(qint64 size) { + auto maxPayload = BasePacket::maxPayloadSize(); + + if (size == -1) { + // default size of -1, means biggest packet possible + size = maxPayload; + } + + // Sanity check + Q_ASSERT(size >= 0 || size < maxPayload); + + _packetSize = size; + _packet.reset(new char[_packetSize]); + _payloadCapacity = _packetSize; + _payloadSize = 0; + _payloadStart = _packet.get(); +} + +BasePacket::BasePacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr) : + _packetSize(size), + _packet(std::move(data)), + _payloadStart(_packet.get()), + _payloadCapacity(size), + _payloadSize(size), + _senderSockAddr(senderSockAddr) +{ + +} + +BasePacket::BasePacket(const BasePacket& other) : + QIODevice() +{ + *this = other; +} + +BasePacket& BasePacket::operator=(const BasePacket& other) { + _packetSize = other._packetSize; + _packet = std::unique_ptr(new char[_packetSize]); + memcpy(_packet.get(), other._packet.get(), _packetSize); + + _payloadStart = _packet.get() + (other._payloadStart - other._packet.get()); + _payloadCapacity = other._payloadCapacity; + + _payloadSize = other._payloadSize; + + _senderSockAddr = other._senderSockAddr; + + if (other.isOpen() && !isOpen()) { + open(other.openMode()); + } + + seek(other.pos()); + + return *this; +} + +BasePacket::BasePacket(BasePacket&& other) { + *this = std::move(other); +} + +BasePacket& BasePacket::operator=(BasePacket&& other) { + _packetSize = other._packetSize; + _packet = std::move(other._packet); + + _payloadStart = other._payloadStart; + _payloadCapacity = other._payloadCapacity; + + _payloadSize = other._payloadSize; + + _senderSockAddr = std::move(other._senderSockAddr); + + if (other.isOpen() && !isOpen()) { + open(other.openMode()); + } + + seek(other.pos()); + + return *this; +} + +qint64 BasePacket::getDataSize() const { + return (_payloadStart - _packet.get()) + _payloadSize; +} + +void BasePacket::setPayloadSize(qint64 payloadSize) { + if (isWritable()) { + Q_ASSERT(payloadSize <= _payloadCapacity); + _payloadSize = payloadSize; + } else { + qDebug() << "You can not call setPayloadSize for a non-writeable Packet."; + Q_ASSERT(false); + } +} + +QByteArray BasePacket::read(qint64 maxSize) { + qint64 sizeToRead = std::min(size() - pos(), maxSize); + QByteArray data { getPayload() + pos(), (int) sizeToRead }; + seek(pos() + sizeToRead); + return data; +} + +QByteArray BasePacket::readWithoutCopy(qint64 maxSize) { + qint64 sizeToRead = std::min(size() - pos(), maxSize); + QByteArray data { QByteArray::fromRawData(getPayload() + pos(), sizeToRead) }; + seek(pos() + sizeToRead); + return data; +} + +bool BasePacket::reset() { + if (isWritable()) { + _payloadSize = 0; + } + + return QIODevice::reset(); +} + +qint64 BasePacket::writeData(const char* data, qint64 maxSize) { + + Q_ASSERT_X(maxSize <= bytesAvailableForWrite(), "BasePacket::writeData", "not enough space for write"); + + // make sure we have the space required to write this block + if (maxSize <= bytesAvailableForWrite()) { + qint64 currentPos = pos(); + + // good to go - write the data + memcpy(_payloadStart + currentPos, data, maxSize); + + // keep track of _payloadSize so we can just write the actual data when packet is about to be sent + _payloadSize = std::max(currentPos + maxSize, _payloadSize); + + // return the number of bytes written + return maxSize; + } else { + // not enough space left for this write - return an error + return PACKET_WRITE_ERROR; + } +} + +qint64 BasePacket::readData(char* dest, qint64 maxSize) { + // we're either reading what is left from the current position or what was asked to be read + qint64 numBytesToRead = std::min(bytesLeftToRead(), maxSize); + + if (numBytesToRead > 0) { + int currentPosition = pos(); + + // read out the data + memcpy(dest, _payloadStart + currentPosition, numBytesToRead); + } + + return numBytesToRead; +} + +void BasePacket::adjustPayloadStartAndCapacity(qint64 headerSize, bool shouldDecreasePayloadSize) { + _payloadStart += headerSize; + _payloadCapacity -= headerSize; + + if (shouldDecreasePayloadSize) { + _payloadSize -= headerSize; + } +} diff --git a/libraries/networking/src/udt/BasePacket.h b/libraries/networking/src/udt/BasePacket.h new file mode 100644 index 0000000000..4e13d99013 --- /dev/null +++ b/libraries/networking/src/udt/BasePacket.h @@ -0,0 +1,125 @@ +// +// BasePacket.h +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-23. +// 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 +// + +#pragma once + +#ifndef hifi_BasePacket_h +#define hifi_BasePacket_h + +#include + +#include + +#include "../HifiSockAddr.h" +#include "Constants.h" + +namespace udt { + +class BasePacket : public QIODevice { + Q_OBJECT +public: + static const qint64 PACKET_WRITE_ERROR; + + static std::unique_ptr create(qint64 size = -1); + static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, + const HifiSockAddr& senderSockAddr); + + + + // Current level's header size + static int localHeaderSize(); + // Cumulated size of all the headers + static int totalHeaderSize(); + // The maximum payload size this packet can use to fit in MTU + static int maxPayloadSize(); + + // Payload direct access to the payload, use responsibly! + char* getPayload() { return _payloadStart; } + const char* getPayload() const { return _payloadStart; } + + // Return direct access to the entire packet, use responsibly! + char* getData() { return _packet.get(); } + const char* getData() const { return _packet.get(); } + + // Returns the size of the packet, including the header + qint64 getDataSize() const; + + // Returns the size of the payload only + qint64 getPayloadSize() const { return _payloadSize; } + + // Allows a writer to change the size of the payload used when writing directly + void setPayloadSize(qint64 payloadSize); + + // Returns the number of bytes allocated for the payload + qint64 getPayloadCapacity() const { return _payloadCapacity; } + + qint64 bytesLeftToRead() const { return _payloadSize - pos(); } + qint64 bytesAvailableForWrite() const { return _payloadCapacity - pos(); } + + HifiSockAddr& getSenderSockAddr() { return _senderSockAddr; } + const HifiSockAddr& getSenderSockAddr() const { return _senderSockAddr; } + + // QIODevice virtual functions + // WARNING: Those methods all refer to the payload ONLY and NOT the entire packet + virtual bool isSequential() const { return false; } + virtual bool reset(); + virtual qint64 size() const { return _payloadCapacity; } + + using QIODevice::read; + QByteArray read(qint64 maxSize); + QByteArray readWithoutCopy(qint64 maxSize); // this can only be used if packet will stay in scope + + template qint64 peekPrimitive(T* data); + template qint64 readPrimitive(T* data); + template qint64 writePrimitive(const T& data); + +protected: + BasePacket(qint64 size); + BasePacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); + BasePacket(const BasePacket& other); + BasePacket& operator=(const BasePacket& other); + BasePacket(BasePacket&& other); + BasePacket& operator=(BasePacket&& other); + + // QIODevice virtual functions + virtual qint64 writeData(const char* data, qint64 maxSize); + virtual qint64 readData(char* data, qint64 maxSize); + + void adjustPayloadStartAndCapacity(qint64 headerSize, bool shouldDecreasePayloadSize = false); + + qint64 _packetSize = 0; // Total size of the allocated memory + std::unique_ptr _packet; // Allocated memory + + char* _payloadStart = nullptr; // Start of the payload + qint64 _payloadCapacity = 0; // Total capacity of the payload + + qint64 _payloadSize = 0; // How much of the payload is actually used + + HifiSockAddr _senderSockAddr; // sender address for packet (only used on receiving end) +}; + +template qint64 BasePacket::peekPrimitive(T* data) { + return QIODevice::peek(reinterpret_cast(data), sizeof(T)); +} + +template qint64 BasePacket::readPrimitive(T* data) { + return QIODevice::read(reinterpret_cast(data), sizeof(T)); +} + +template qint64 BasePacket::writePrimitive(const T& data) { + static_assert(!std::is_pointer::value, "T must not be a pointer"); + return QIODevice::write(reinterpret_cast(&data), sizeof(T)); +} + +} // namespace udt + + +#endif // hifi_BasePacket_h diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp new file mode 100644 index 0000000000..9dc1880df6 --- /dev/null +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -0,0 +1,207 @@ +// +// CongestionControl.cpp +// libraries/networking/src/udt +// +// Created by Clement on 7/23/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 "CongestionControl.h" + +#include + +#include "Packet.h" + +using namespace udt; +using namespace std::chrono; + +static const double USECS_PER_SECOND = 1000000.0; + +void CongestionControl::setPacketSendPeriod(double newSendPeriod) { + Q_ASSERT_X(newSendPeriod >= 0, "CongestionControl::setPacketPeriod", "Can not set a negative packet send period"); + + if (_maxBandwidth > 0) { + // anytime the packet send period is about to be increased, make sure it stays below the minimum period, + // calculated based on the maximum desired bandwidth + double minPacketSendPeriod = USECS_PER_SECOND / (((double) _maxBandwidth) / _mss); + _packetSendPeriod = std::max(newSendPeriod, minPacketSendPeriod); + } else { + _packetSendPeriod = newSendPeriod; + } +} + +DefaultCC::DefaultCC() : + _lastRCTime(high_resolution_clock::now()), + _slowStartLastAck(_sendCurrSeqNum), + _lastDecreaseMaxSeq(SequenceNumber {SequenceNumber::MAX }) +{ + _mss = udt::MAX_PACKET_SIZE_WITH_UDP_HEADER; + + _congestionWindowSize = 16.0; + _packetSendPeriod = 1.0; +} + +void DefaultCC::onACK(SequenceNumber ackNum) { + double increase = 0; + + // Note from UDT original code: + // The minimum increase parameter is increased from "1.0 / _mss" to 0.01 + // because the original was too small and caused sending rate to stay at low level + // for long time. + const double minimumIncrease = 0.01; + + // we will only adjust once per sync interval so check that it has been at least that long now + auto now = high_resolution_clock::now(); + if (duration_cast(now - _lastRCTime).count() < synInterval()) { + return; + } + + // our last rate increase time is now + _lastRCTime = now; + + if (_slowStart) { + // we are in slow start phase - increase the congestion window size by the number of packets just ACKed + _congestionWindowSize += seqlen(_slowStartLastAck, ackNum); + + // update the last ACK + _slowStartLastAck = ackNum; + + // check if we can get out of slow start (is our new congestion window size bigger than the max) + if (_congestionWindowSize > _maxCongestionWindowSize) { + _slowStart = false; + + if (_receiveRate > 0) { + // if we have a valid receive rate we set the send period to whatever the receive rate dictates + _packetSendPeriod = USECS_PER_SECOND / _receiveRate; + } else { + // no valid receive rate, packet send period is dictated by estimated RTT and current congestion window size + _packetSendPeriod = (_rtt + synInterval()) / _congestionWindowSize; + } + } + } else { + // not in slow start - window size should be arrival rate * (RTT + SYN) + 16 + _congestionWindowSize = _receiveRate / USECS_PER_SECOND * (_rtt + synInterval()) + 16; + } + + // during slow start we perform no rate increases + if (_slowStart) { + return; + } + + // if loss has happened since the last rate increase we do not perform another increase + if (_loss) { + _loss = false; + return; + } + + double capacitySpeedDelta = (_bandwidth - USECS_PER_SECOND / _packetSendPeriod); + + // UDT uses what they call DAIMD - additive increase multiplicative decrease with decreasing increases + // This factor is a protocol parameter that is part of the DAIMD algorithim + static const int AIMD_DECREASING_INCREASE_FACTOR = 9; + + if ((_packetSendPeriod > _lastDecreasePeriod) && ((_bandwidth / AIMD_DECREASING_INCREASE_FACTOR) < capacitySpeedDelta)) { + capacitySpeedDelta = _bandwidth / AIMD_DECREASING_INCREASE_FACTOR; + } + + if (capacitySpeedDelta <= 0) { + increase = minimumIncrease; + } else { + // use UDTs DAIMD algorithm to figure out what the send period increase factor should be + + // inc = max(10 ^ ceil(log10(B * MSS * 8 ) * Beta / MSS, minimumIncrease) + // B = estimated link capacity + // Beta = 1.5 * 10^(-6) + + static const double BETA = 0.0000015; + static const double BITS_PER_BYTE = 8.0; + + increase = pow(10.0, ceil(log10(capacitySpeedDelta * _mss * BITS_PER_BYTE))) * BETA / _mss; + + if (increase < minimumIncrease) { + increase = minimumIncrease; + } + } + + setPacketSendPeriod((_packetSendPeriod * synInterval()) / (_packetSendPeriod * increase + synInterval())); +} + +void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) { + // stop the slow start if we haven't yet + if (_slowStart) { + stopSlowStart(); + + // if the change to send rate was driven by a known receive rate, then we don't continue with the decrease + if (_receiveRate > 0) { + return; + } + } + + _loss = true; + + static const double INTER_PACKET_ARRIVAL_INCREASE = 1.125; + static const int MAX_DECREASES_PER_CONGESTION_EPOCH = 5; + + // check if this NAK starts a new congestion period - this will be the case if the + // NAK received occured for a packet sent after the last decrease + if (rangeStart > _lastDecreaseMaxSeq) { + _lastDecreasePeriod = _packetSendPeriod; + + _packetSendPeriod = ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE); + + // use EWMA to update the average number of NAKs per congestion + static const double NAK_EWMA_ALPHA = 0.125; + _avgNAKNum = (int)ceil(_avgNAKNum * (1 - NAK_EWMA_ALPHA) + _nakCount * NAK_EWMA_ALPHA); + + // update the count of NAKs and count of decreases in this interval + _nakCount = 1; + _decreaseCount = 1; + + _lastDecreaseMaxSeq = _sendCurrSeqNum; + + // avoid synchronous rate decrease across connections using randomization + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution<> distribution(1, _avgNAKNum); + + _randomDecreaseThreshold = distribution(generator); + + } else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((++_nakCount % _randomDecreaseThreshold) == 0)) { + // there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we + // decided we would decrease the packet send period + + _packetSendPeriod = ceil(_packetSendPeriod * INTER_PACKET_ARRIVAL_INCREASE); + _lastDecreaseMaxSeq = _sendCurrSeqNum; + } +} + +// Note: This isn't currently being called by anything since we, unlike UDT, don't have TTL on our packets +void DefaultCC::onTimeout() { + if (_slowStart) { + stopSlowStart(); + } else { + // UDT used to do the following on timeout if not in slow start - we should check if it could be helpful + // _lastDecreasePeriod = _packetSendPeriod; + // _packetSendPeriod = ceil(_packetSendPeriod * 2); + + // this seems odd - the last ack they were setting _lastDecreaseMaxSeq to only applies to slow start + // _lastDecreaseMaxSeq = _slowStartLastAck; + } +} + +void DefaultCC::stopSlowStart() { + _slowStart = false; + + if (_receiveRate > 0) { + // Set the sending rate to the receiving rate. + _packetSendPeriod = USECS_PER_SECOND / _receiveRate; + } else { + // If no receiving rate is observed, we have to compute the sending + // rate according to the current window size, and decrease it + // using the method below. + _packetSendPeriod = _congestionWindowSize / (_rtt + synInterval()); + } +} diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h new file mode 100644 index 0000000000..8fd1f22bc8 --- /dev/null +++ b/libraries/networking/src/udt/CongestionControl.h @@ -0,0 +1,124 @@ +// +// CongestionControl.h +// libraries/networking/src/udt +// +// Created by Clement on 7/23/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 +// + +#ifndef hifi_CongestionControl_h +#define hifi_CongestionControl_h + +#include +#include +#include +#include + +#include "LossList.h" +#include "SequenceNumber.h" + +namespace udt { + +static const int32_t DEFAULT_SYN_INTERVAL = 10000; // 10 ms + +class Connection; +class Packet; + +class CongestionControl { + friend class Connection; +public: + + CongestionControl() {}; + CongestionControl(int synInterval) : _synInterval(synInterval) {} + virtual ~CongestionControl() {} + + int synInterval() const { return _synInterval; } + + virtual void init() {} + virtual void onACK(SequenceNumber ackNum) {} + virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {} + +protected: + void setAckInterval(int ackInterval) { _ackInterval = ackInterval; } + void setRTO(int rto) { _userDefinedRTO = true; _rto = rto; } + + void setMSS(int mss) { _mss = mss; } + void setMaxCongestionWindowSize(int window) { _maxCongestionWindowSize = window; } + void setBandwidth(int bandwidth) { _bandwidth = bandwidth; } + void setMaxBandwidth(int maxBandwidth) { _maxBandwidth = maxBandwidth; } + void setSendCurrentSequenceNumber(SequenceNumber seqNum) { _sendCurrSeqNum = seqNum; } + void setReceiveRate(int rate) { _receiveRate = rate; } + void setRTT(int rtt) { _rtt = rtt; } + void setPacketSendPeriod(double newSendPeriod); // call this internally to ensure send period doesn't go past max bandwidth + + double _packetSendPeriod { 1.0 }; // Packet sending period, in microseconds + double _congestionWindowSize { 16.0 }; // Congestion window size, in packets + + int _bandwidth { 0 }; // estimated bandwidth, packets per second + int _maxBandwidth { -1 }; // Maximum desired bandwidth, packets per second + double _maxCongestionWindowSize { 0.0 }; // maximum cwnd size, in packets + + int _mss { 0 }; // Maximum Packet Size, including all packet headers + SequenceNumber _sendCurrSeqNum; // current maximum seq num sent out + int _receiveRate { 0 }; // packet arrive rate at receiver side, packets per second + int _rtt { 0 }; // current estimated RTT, microsecond + +private: + CongestionControl(const CongestionControl& other) = delete; + CongestionControl& operator=(const CongestionControl& other) = delete; + + int _ackInterval { 0 }; // How many packets to send one ACK, in packets + int _lightACKInterval { 64 }; // How many packets to send one light ACK, in packets + + int _synInterval { DEFAULT_SYN_INTERVAL }; + + bool _userDefinedRTO { false }; // if the RTO value is defined by users + int _rto { -1 }; // RTO value, microseconds +}; + + +class CongestionControlVirtualFactory { +public: + virtual ~CongestionControlVirtualFactory() {} + + static int synInterval() { return DEFAULT_SYN_INTERVAL; } + + virtual std::unique_ptr create() = 0; +}; + +template class CongestionControlFactory: public CongestionControlVirtualFactory { +public: + virtual ~CongestionControlFactory() {} + virtual std::unique_ptr create() { return std::unique_ptr(new T()); } +}; + +class DefaultCC: public CongestionControl { +public: + DefaultCC(); + +public: + virtual void onACK(SequenceNumber ackNum); + virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd); + virtual void onTimeout(); + +private: + void stopSlowStart(); // stops the slow start on loss or timeout + + std::chrono::high_resolution_clock::time_point _lastRCTime; // last rate increase time + bool _slowStart { true }; // if in slow start phase + SequenceNumber _slowStartLastAck; // last ACKed seq num + bool _loss { false }; // if loss happened since last rate increase + SequenceNumber _lastDecreaseMaxSeq; // max pkt seq num sent out when last decrease happened + double _lastDecreasePeriod { 1 }; // value of _packetSendPeriod when last decrease happened + int _nakCount { 0 }; // number of NAKs in congestion epoch + int _randomDecreaseThreshold { 1 }; // random threshold on decrease by number of loss events + int _avgNAKNum { 0 }; // average number of NAKs per congestion + int _decreaseCount { 0 }; // number of decreases in a congestion epoch +}; + +} + +#endif // hifi_CongestionControl_h diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp new file mode 100644 index 0000000000..84928fa298 --- /dev/null +++ b/libraries/networking/src/udt/Connection.cpp @@ -0,0 +1,886 @@ +// +// Connection.cpp +// libraries/networking/src/udt +// +// Created by Clement on 7/27/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 "Connection.h" + +#include + +#include + +#include "../HifiSockAddr.h" +#include "../NetworkLogging.h" + +#include "CongestionControl.h" +#include "ControlPacket.h" +#include "Packet.h" +#include "PacketList.h" +#include "Socket.h" + +using namespace udt; +using namespace std::chrono; + +Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl) : + _connectionStart(high_resolution_clock::now()), + _parentSocket(parentSocket), + _destination(destination), + _congestionControl(move(congestionControl)) +{ + Q_ASSERT_X(socket, "Connection::Connection", "Must be called with a valid Socket*"); + + Q_ASSERT_X(_congestionControl, "Connection::Connection", "Must be called with a valid CongestionControl object"); + _congestionControl->init(); + + // setup default SYN, RTT and RTT Variance based on the SYN interval in CongestionControl object + _synInterval = _congestionControl->synInterval(); + + resetRTT(); + + // set the initial RTT and flow window size on congestion control object + _congestionControl->setRTT(_rtt); + _congestionControl->setMaxCongestionWindowSize(_flowWindowSize); +} + +Connection::~Connection() { + stopSendQueue(); +} + +void Connection::stopSendQueue() { + if (_sendQueue) { + // grab the send queue thread so we can wait on it + QThread* sendQueueThread = _sendQueue->thread(); + + // since we're stopping the send queue we should consider our handshake ACK not receieved + _hasReceivedHandshakeACK = false; + + // tell the send queue to stop and be deleted + _sendQueue->stop(); + _sendQueue->deleteLater(); + _sendQueue.release(); + + // wait on the send queue thread so we know the send queue is gone + sendQueueThread->quit(); + sendQueueThread->wait(); + } +} + +void Connection::resetRTT() { + _rtt = _synInterval * 10; + _rttVariance = _rtt / 2; +} + +SendQueue& Connection::getSendQueue() { + if (!_sendQueue) { + // Lasily create send queue + _sendQueue = SendQueue::create(_parentSocket, _destination); + + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Created SendQueue for connection to" << _destination; + #endif + + QObject::connect(_sendQueue.get(), &SendQueue::packetSent, this, &Connection::packetSent); + QObject::connect(_sendQueue.get(), &SendQueue::packetSent, this, &Connection::recordSentPackets); + QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission); + QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive); + + // set defaults on the send queue from our congestion control object and estimatedTimeout() + _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); + _sendQueue->setEstimatedTimeout(estimatedTimeout()); + _sendQueue->setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); + } + + return *_sendQueue; +} + +void Connection::queueInactive() { + // tell our current send queue to go down and reset our ptr to it to null + stopSendQueue(); + + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Connection to" << _destination << "has stopped its SendQueue."; + #endif + + if (!_hasReceivedHandshake || !_isReceivingData) { + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Connection SendQueue to" << _destination << "stopped and no data is being received - stopping connection."; + #endif + + emit connectionInactive(_destination); + } +} + +void Connection::sendReliablePacket(std::unique_ptr packet) { + Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); + getSendQueue().queuePacket(std::move(packet)); +} + +void Connection::sendReliablePacketList(std::unique_ptr packetList) { + Q_ASSERT_X(packetList->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); + getSendQueue().queuePacketList(std::move(packetList)); +} + +void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { + Q_ASSERT(packet->isPartOfMessage()); + + auto messageNumber = packet->getMessageNumber(); + PendingReceivedMessage& pendingMessage = _pendingReceivedMessages[messageNumber]; + + pendingMessage.enqueuePacket(std::move(packet)); + + if (pendingMessage.isComplete()) { + // All messages have been received, create PacketList + auto packetList = PacketList::fromReceivedPackets(std::move(pendingMessage._packets)); + + _pendingReceivedMessages.erase(messageNumber); + + if (_parentSocket) { + _parentSocket->messageReceived(std::move(packetList)); + } + } +} + +void Connection::sync() { + if (_isReceivingData) { + + // check if we should expire the receive portion of this connection + // this occurs if it has been 16 timeouts since the last data received and at least 5 seconds + static const int NUM_TIMEOUTS_BEFORE_EXPIRY = 16; + static const int MIN_SECONDS_BEFORE_EXPIRY = 5; + + auto now = high_resolution_clock::now(); + + auto sincePacketReceive = now - _lastReceiveTime; + + if (duration_cast(sincePacketReceive).count() >= NUM_TIMEOUTS_BEFORE_EXPIRY * estimatedTimeout() + && duration_cast(sincePacketReceive).count() >= MIN_SECONDS_BEFORE_EXPIRY ) { + // the receive side of this connection is expired + _isReceivingData = false; + + // if we don't have a send queue that means the whole connection has expired and we can emit our signal + // otherwise we'll wait for it to also timeout before cleaning up + if (!_sendQueue) { + + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Connection to" << _destination << "no longer receiving any data and there is currently no send queue - stopping connection."; + #endif + + emit connectionInactive(_destination); + } + } + + // reset the number of light ACKs or non SYN ACKs during this sync interval + _lightACKsDuringSYN = 1; + _acksDuringSYN = 1; + + // we send out a periodic ACK every rate control interval + sendACK(); + + if (_lossList.getLength() > 0) { + // check if we need to re-transmit a loss list + // we do this if it has been longer than the current nakInterval since we last sent + auto now = high_resolution_clock::now(); + + if (duration_cast(now - _lastNAKTime).count() >= _nakInterval) { + // Send a timeout NAK packet + sendTimeoutNAK(); + } + } + } else if (!_sendQueue) { + // we haven't received a packet and we're not sending + // this most likely means we were started erroneously + // check the start time for this connection and auto expire it after 5 seconds of not receiving or sending any data + static const int CONNECTION_NOT_USED_EXPIRY_SECONDS = 5; + auto secondsSinceStart = duration_cast(high_resolution_clock::now() - _connectionStart).count(); + + if (secondsSinceStart >= CONNECTION_NOT_USED_EXPIRY_SECONDS) { + // it's been CONNECTION_NOT_USED_EXPIRY_SECONDS and nothing has actually happened with this connection + // consider it inactive and emit our inactivity signal + + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Connection to" << _destination << "did not receive or send any data in last" + << CONNECTION_NOT_USED_EXPIRY_SECONDS << "seconds - stopping connection."; + #endif + + emit connectionInactive(_destination); + } + } +} + +void Connection::recordSentPackets(int dataSize, int payloadSize) { + _stats.recordSentPackets(payloadSize, dataSize); +} + +void Connection::recordRetransmission() { + _stats.record(ConnectionStats::Stats::Retransmission); +} + +void Connection::sendACK(bool wasCausedBySyncTimeout) { + static high_resolution_clock::time_point lastACKSendTime; + auto currentTime = high_resolution_clock::now(); + + SequenceNumber nextACKNumber = nextACK(); + Q_ASSERT_X(nextACKNumber >= _lastSentACK, "Connection::sendACK", "Sending lower ACK, something is wrong"); + + if (nextACKNumber == _lastSentACK) { + // We already sent this ACK, but check if we should re-send it. + if (nextACKNumber < _lastReceivedAcknowledgedACK) { + // we already got an ACK2 for this ACK we would be sending, don't bother + return; + } + + // We will re-send if it has been more than the estimated timeout since the last ACK + microseconds sinceLastACK = duration_cast(currentTime - lastACKSendTime); + + if (sinceLastACK.count() < estimatedTimeout()) { + return; + } + } + // we have received new packets since the last sent ACK + + // update the last sent ACK + _lastSentACK = nextACKNumber; + + // setup the ACK packet, make it static so we can re-use it + static const int ACK_PACKET_PAYLOAD_BYTES = sizeof(_lastSentACK) + sizeof(_currentACKSubSequenceNumber) + + sizeof(_rtt) + sizeof(int32_t) + sizeof(int32_t) + sizeof(int32_t); + static auto ackPacket = ControlPacket::create(ControlPacket::ACK, ACK_PACKET_PAYLOAD_BYTES); + ackPacket->reset(); // We need to reset it every time. + + // pack in the ACK sub-sequence number + ackPacket->writePrimitive(++_currentACKSubSequenceNumber); + + // pack in the ACK number + ackPacket->writePrimitive(nextACKNumber); + + // pack in the RTT and variance + ackPacket->writePrimitive(_rtt); + + // pack the available buffer size, in packets + // in our implementation we have no hard limit on receive buffer size, send the default value + ackPacket->writePrimitive((int32_t) udt::CONNECTION_RECEIVE_BUFFER_SIZE_PACKETS); + + if (wasCausedBySyncTimeout) { + // grab the up to date packet receive speed and estimated bandwidth + int32_t packetReceiveSpeed = _receiveWindow.getPacketReceiveSpeed(); + int32_t estimatedBandwidth = _receiveWindow.getEstimatedBandwidth(); + + // update those values in our connection stats + _stats.recordReceiveRate(packetReceiveSpeed); + _stats.recordEstimatedBandwidth(estimatedBandwidth); + + // pack in the receive speed and estimatedBandwidth + ackPacket->writePrimitive(packetReceiveSpeed); + ackPacket->writePrimitive(estimatedBandwidth); + + // record this as the last ACK send time + lastACKSendTime = high_resolution_clock::now(); + } + + // have the socket send off our packet + _parentSocket->writeBasePacket(*ackPacket, _destination); + + Q_ASSERT_X(_sentACKs.empty() || _sentACKs.back().first + 1 == _currentACKSubSequenceNumber, + "Connection::sendACK", "Adding an invalid ACK to _sentACKs"); + + // write this ACK to the map of sent ACKs + _sentACKs.push_back({ _currentACKSubSequenceNumber, { nextACKNumber, high_resolution_clock::now() }}); + + // reset the number of data packets received since last ACK + _packetsSinceACK = 0; + + _stats.record(ConnectionStats::Stats::SentACK); +} + +void Connection::sendLightACK() { + SequenceNumber nextACKNumber = nextACK(); + + if (nextACKNumber == _lastReceivedAcknowledgedACK) { + // we already got an ACK2 for this ACK we would be sending, don't bother + return; + } + + // create the light ACK packet, make it static so we can re-use it + static const int LIGHT_ACK_PACKET_PAYLOAD_BYTES = sizeof(SequenceNumber); + static auto lightACKPacket = ControlPacket::create(ControlPacket::LightACK, LIGHT_ACK_PACKET_PAYLOAD_BYTES); + + // reset the lightACKPacket before we go to write the ACK to it + lightACKPacket->reset(); + + // pack in the ACK + lightACKPacket->writePrimitive(nextACKNumber); + + // have the socket send off our packet immediately + _parentSocket->writeBasePacket(*lightACKPacket, _destination); + + _stats.record(ConnectionStats::Stats::SentLightACK); +} + +void Connection::sendACK2(SequenceNumber currentACKSubSequenceNumber) { + // setup a static ACK2 packet we will re-use + static const int ACK2_PAYLOAD_BYTES = sizeof(SequenceNumber); + static auto ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); + + // reset the ACK2 Packet before writing the sub-sequence number to it + ack2Packet->reset(); + + // write the sub sequence number for this ACK2 + ack2Packet->writePrimitive(currentACKSubSequenceNumber); + + // send the ACK2 packet + _parentSocket->writeBasePacket(*ack2Packet, _destination); + + // update the last sent ACK2 and the last ACK2 send time + _lastSentACK2 = currentACKSubSequenceNumber; + + _stats.record(ConnectionStats::Stats::SentACK2); +} + +void Connection::sendNAK(SequenceNumber sequenceNumberRecieved) { + // create the loss report packet, make it static so we can re-use it + static const int NAK_PACKET_PAYLOAD_BYTES = 2 * sizeof(SequenceNumber); + static auto lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); + lossReport->reset(); // We need to reset it every time. + + // pack in the loss report + lossReport->writePrimitive(_lastReceivedSequenceNumber + 1); + if (_lastReceivedSequenceNumber + 1 != sequenceNumberRecieved - 1) { + lossReport->writePrimitive(sequenceNumberRecieved - 1); + } + + // have the parent socket send off our packet immediately + _parentSocket->writeBasePacket(*lossReport, _destination); + + // record our last NAK time + _lastNAKTime = high_resolution_clock::now(); + + _stats.record(ConnectionStats::Stats::SentNAK); +} + +void Connection::sendTimeoutNAK() { + if (_lossList.getLength() > 0) { + + int timeoutPayloadSize = std::min((int) (_lossList.getLength() * 2 * sizeof(SequenceNumber)), + ControlPacket::maxPayloadSize()); + + // construct a NAK packet that will hold all of the lost sequence numbers + auto lossListPacket = ControlPacket::create(ControlPacket::TimeoutNAK, timeoutPayloadSize); + + // Pack in the lost sequence numbers + _lossList.write(*lossListPacket, timeoutPayloadSize / (2 * sizeof(SequenceNumber))); + + // have our parent socket send off this control packet + _parentSocket->writeBasePacket(*lossListPacket, _destination); + + // record this as the last NAK time + _lastNAKTime = high_resolution_clock::now(); + + _stats.record(ConnectionStats::Stats::SentTimeoutNAK); + } +} + +SequenceNumber Connection::nextACK() const { + if (_lossList.getLength() > 0) { + return _lossList.getFirstSequenceNumber() - 1; + } else { + return _lastReceivedSequenceNumber; + } +} + +bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize) { + + if (!_hasReceivedHandshake) { + // refuse to process any packets until we've received the handshake + return false; + } + + _isReceivingData = true; + + // mark our last receive time as now (to push the potential expiry farther) + _lastReceiveTime = high_resolution_clock::now(); + + // check if this is a packet pair we should estimate bandwidth from, or just a regular packet + if (((uint32_t) sequenceNumber & 0xF) == 0) { + _receiveWindow.onProbePair1Arrival(); + } else if (((uint32_t) sequenceNumber & 0xF) == 1) { + // only use this packet for bandwidth estimation if we didn't just receive a control packet in its place + if (!_receivedControlProbeTail) { + _receiveWindow.onProbePair2Arrival(); + } else { + // reset our control probe tail marker so the next probe that comes with data can be used + _receivedControlProbeTail = false; + } + + } + _receiveWindow.onPacketArrival(); + + // If this is not the next sequence number, report loss + if (sequenceNumber > _lastReceivedSequenceNumber + 1) { + if (_lastReceivedSequenceNumber + 1 == sequenceNumber - 1) { + _lossList.append(_lastReceivedSequenceNumber + 1); + } else { + _lossList.append(_lastReceivedSequenceNumber + 1, sequenceNumber - 1); + } + + // Send a NAK packet + sendNAK(sequenceNumber); + + // figure out when we should send the next loss report, if we haven't heard anything back + _nakInterval = estimatedTimeout(); + + int receivedPacketsPerSecond = _receiveWindow.getPacketReceiveSpeed(); + if (receivedPacketsPerSecond > 0) { + // the NAK interval is at least the _minNAKInterval + // but might be the time required for all lost packets to be retransmitted + _nakInterval += (int) (_lossList.getLength() * (USECS_PER_SECOND / receivedPacketsPerSecond)); + } + + // the NAK interval is at least the _minNAKInterval but might be the value calculated above, if that is larger + _nakInterval = std::max(_nakInterval, _minNAKInterval); + + } + + bool wasDuplicate = false; + + if (sequenceNumber > _lastReceivedSequenceNumber) { + // Update largest recieved sequence number + _lastReceivedSequenceNumber = sequenceNumber; + } else { + // Otherwise, it could be a resend, try and remove it from the loss list + wasDuplicate = !_lossList.remove(sequenceNumber); + } + + // increment the counters for data packets received + ++_packetsSinceACK; + + // check if we need to send an ACK, according to CC params + if (_congestionControl->_ackInterval > 0 && _packetsSinceACK >= _congestionControl->_ackInterval * _acksDuringSYN) { + _acksDuringSYN++; + sendACK(false); + } else if (_congestionControl->_lightACKInterval > 0 + && _packetsSinceACK >= _congestionControl->_lightACKInterval * _lightACKsDuringSYN) { + sendLightACK(); + ++_lightACKsDuringSYN; + } + + if (wasDuplicate) { + _stats.record(ConnectionStats::Stats::Duplicate); + } else { + _stats.recordReceivedPackets(payloadSize, packetSize); + } + + return !wasDuplicate; +} + +void Connection::processControl(std::unique_ptr controlPacket) { + + // Simple dispatch to control packets processing methods based on their type. + + // Processing of control packets (other than Handshake / Handshake ACK) + // is not performed if the handshake has not been completed. + + switch (controlPacket->getType()) { + case ControlPacket::ACK: + if (_hasReceivedHandshakeACK) { + processACK(move(controlPacket)); + } + break; + case ControlPacket::LightACK: + if (_hasReceivedHandshakeACK) { + processLightACK(move(controlPacket)); + } + break; + case ControlPacket::ACK2: + if (_hasReceivedHandshake) { + processACK2(move(controlPacket)); + } + break; + case ControlPacket::NAK: + if (_hasReceivedHandshakeACK) { + processNAK(move(controlPacket)); + } + break; + case ControlPacket::TimeoutNAK: + if (_hasReceivedHandshakeACK) { + processTimeoutNAK(move(controlPacket)); + } + break; + case ControlPacket::Handshake: + processHandshake(move(controlPacket)); + break; + case ControlPacket::HandshakeACK: + processHandshakeACK(move(controlPacket)); + break; + case ControlPacket::ProbeTail: + if (_isReceivingData) { + processProbeTail(move(controlPacket)); + } + break; + } +} + +void Connection::processACK(std::unique_ptr controlPacket) { + // read the ACK sub-sequence number + SequenceNumber currentACKSubSequenceNumber; + controlPacket->readPrimitive(¤tACKSubSequenceNumber); + + // Check if we need send an ACK2 for this ACK + // This will be the case if it has been longer than the sync interval OR + // it looks like they haven't received our ACK2 for this ACK + auto currentTime = high_resolution_clock::now(); + static high_resolution_clock::time_point lastACK2SendTime; + + microseconds sinceLastACK2 = duration_cast(currentTime - lastACK2SendTime); + + if (sinceLastACK2.count() >= _synInterval || currentACKSubSequenceNumber == _lastSentACK2) { + // Send ACK2 packet + sendACK2(currentACKSubSequenceNumber); + + lastACK2SendTime = high_resolution_clock::now(); + } + + // read the ACKed sequence number + SequenceNumber ack; + controlPacket->readPrimitive(&ack); + + // update the total count of received ACKs + _stats.record(ConnectionStats::Stats::ReceivedACK); + + // validate that this isn't a BS ACK + if (ack > getSendQueue().getCurrentSequenceNumber()) { + // in UDT they specifically break the connection here - do we want to do anything? + Q_ASSERT_X(false, "Connection::processACK", "ACK recieved higher than largest sent sequence number"); + return; + } + + // read the RTT + int32_t rtt; + controlPacket->readPrimitive(&rtt); + + if (ack < _lastReceivedACK) { + // this is an out of order ACK, bail + return; + } + + // this is a valid ACKed sequence number - update the flow window size and the last received ACK + int32_t packedFlowWindow; + controlPacket->readPrimitive(&packedFlowWindow); + + _flowWindowSize = packedFlowWindow; + + if (ack == _lastReceivedACK) { + // processing an already received ACK, bail + return; + } + + _lastReceivedACK = ack; + + // ACK the send queue so it knows what was received + getSendQueue().ack(ack); + + // update the RTT + updateRTT(rtt); + + // write this RTT to stats + _stats.recordRTT(rtt); + + // set the RTT for congestion control + _congestionControl->setRTT(_rtt); + + if (controlPacket->bytesLeftToRead() > 0) { + int32_t receiveRate, bandwidth; + + Q_ASSERT_X(controlPacket->bytesLeftToRead() == sizeof(receiveRate) + sizeof(bandwidth), + "Connection::processACK", "sync interval ACK packet does not contain expected data"); + + controlPacket->readPrimitive(&receiveRate); + controlPacket->readPrimitive(&bandwidth); + + // set the delivery rate and bandwidth for congestion control + // these are calculated using an EWMA + static const int EMWA_ALPHA_NUMERATOR = 8; + + // record these samples in connection stats + _stats.recordSendRate(receiveRate); + _stats.recordEstimatedBandwidth(bandwidth); + + _deliveryRate = (_deliveryRate * (EMWA_ALPHA_NUMERATOR - 1) + receiveRate) / EMWA_ALPHA_NUMERATOR; + _bandwidth = (_bandwidth * (EMWA_ALPHA_NUMERATOR - 1) + bandwidth) / EMWA_ALPHA_NUMERATOR; + + _congestionControl->setReceiveRate(_deliveryRate); + _congestionControl->setBandwidth(_bandwidth); + } + + // give this ACK to the congestion control and update the send queue parameters + updateCongestionControlAndSendQueue([this, ack](){ + _congestionControl->onACK(ack); + }); + + _stats.record(ConnectionStats::Stats::ProcessedACK); +} + +void Connection::processLightACK(std::unique_ptr controlPacket) { + // read the ACKed sequence number + SequenceNumber ack; + controlPacket->readPrimitive(&ack); + + // must be larger than the last received ACK to be processed + if (ack > _lastReceivedACK) { + // NOTE: the following makes sense in UDT where there is a dynamic receive buffer. + // Since we have a receive buffer that is always of a default size, we don't use this light ACK to + // drop the flow window size. + + // decrease the flow window size by the offset between the last received ACK and this ACK + // _flowWindowSize -= seqoff(_lastReceivedACK, ack); + + // update the last received ACK to the this one + _lastReceivedACK = ack; + + // send light ACK to the send queue + getSendQueue().ack(ack); + } + + _stats.record(ConnectionStats::Stats::ReceivedLightACK); +} + +void Connection::processACK2(std::unique_ptr controlPacket) { + // pull the sub sequence number from the packet + SequenceNumber subSequenceNumber; + controlPacket->readPrimitive(&subSequenceNumber); + + // check if we had that subsequence number in our map + auto it = std::find_if_not(_sentACKs.begin(), _sentACKs.end(), [&subSequenceNumber](const ACKListPair& pair){ + return pair.first < subSequenceNumber; + }); + + if (it != _sentACKs.end()) { + if (it->first == subSequenceNumber){ + // update the RTT using the ACK window + + // calculate the RTT (time now - time ACK sent) + auto now = high_resolution_clock::now(); + int rtt = duration_cast(now - it->second.second).count(); + + updateRTT(rtt); + // write this RTT to stats + _stats.recordRTT(rtt); + + // set the RTT for congestion control + _congestionControl->setRTT(_rtt); + + // update the last ACKed ACK + if (it->second.first > _lastReceivedAcknowledgedACK) { + _lastReceivedAcknowledgedACK = it->second.first; + } + } else if (it->first < subSequenceNumber) { + Q_UNREACHABLE(); + } + } + + // erase this sub-sequence number and anything below it now that we've gotten our timing information + _sentACKs.erase(_sentACKs.begin(), it); + + _stats.record(ConnectionStats::Stats::ReceivedACK2); +} + +void Connection::processNAK(std::unique_ptr controlPacket) { + // read the loss report + SequenceNumber start, end; + controlPacket->readPrimitive(&start); + + end = start; + + if (controlPacket->bytesLeftToRead() >= (qint64)sizeof(SequenceNumber)) { + controlPacket->readPrimitive(&end); + } + + // send that off to the send queue so it knows there was loss + getSendQueue().nak(start, end); + + // give the loss to the congestion control object and update the send queue parameters + updateCongestionControlAndSendQueue([this, start, end](){ + _congestionControl->onLoss(start, end); + }); + + _stats.record(ConnectionStats::Stats::ReceivedNAK); +} + +void Connection::processHandshake(std::unique_ptr controlPacket) { + + if (!_hasReceivedHandshake || _isReceivingData) { + // server sent us a handshake - we need to assume this means state should be reset + // as long as we haven't received a handshake yet or we have and we've received some data + resetReceiveState(); + } + + // immediately respond with a handshake ACK + static auto handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, 0); + _parentSocket->writeBasePacket(*handshakeACK, _destination); + + // indicate that handshake has been received + _hasReceivedHandshake = true; +} + +void Connection::processHandshakeACK(std::unique_ptr controlPacket) { + // hand off this handshake ACK to the send queue so it knows it can start sending + getSendQueue().handshakeACK(); + + // indicate that handshake ACK was received + _hasReceivedHandshakeACK = true; +} + +void Connection::processTimeoutNAK(std::unique_ptr controlPacket) { + // Override SendQueue's LossList with the timeout NAK list + getSendQueue().overrideNAKListFromPacket(*controlPacket); + + // we don't tell the congestion control object there was loss here - this matches UDTs implementation + // a possible improvement would be to tell it which new loss this timeout packet told us about + + _stats.record(ConnectionStats::Stats::ReceivedTimeoutNAK); +} + +void Connection::processProbeTail(std::unique_ptr controlPacket) { + if (((uint32_t) _lastReceivedSequenceNumber & 0xF) == 0) { + // this is the second packet in a probe set so we can estimate bandwidth + // the sender sent this to us in lieu of sending new data (because they didn't have any) + +#ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Processing second packet of probe from control packet instead of data packet"; +#endif + + _receiveWindow.onProbePair2Arrival(); + + // mark that we processed a control packet for the second in the pair and we should not mark + // the next data packet received + _receivedControlProbeTail = true; + } +} + +void Connection::resetReceiveState() { + + // reset all SequenceNumber member variables back to default + SequenceNumber defaultSequenceNumber; + + _lastReceivedSequenceNumber = defaultSequenceNumber; + + _lastReceivedAcknowledgedACK = defaultSequenceNumber; + _currentACKSubSequenceNumber = defaultSequenceNumber; + + _lastSentACK = defaultSequenceNumber; + + // clear the sent ACKs + _sentACKs.clear(); + + // clear the loss list and _lastNAKTime + _lossList.clear(); + _lastNAKTime = high_resolution_clock::time_point(); + + // the _nakInterval need not be reset, that will happen on loss + + // clear sync variables + _isReceivingData = false; + _connectionStart = high_resolution_clock::now(); + + _acksDuringSYN = 1; + _lightACKsDuringSYN = 1; + _packetsSinceACK = 0; + + // reset RTT to initial value + resetRTT(); + + // clear the intervals in the receive window + _receiveWindow.reset(); + _receivedControlProbeTail = false; + + // clear any pending received messages + _pendingReceivedMessages.clear(); +} + +void Connection::updateRTT(int rtt) { + // This updates the RTT using exponential weighted moving average + // This is the Jacobson's forumla for RTT estimation + // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf + + // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) + // (where x = 0.125 via Jacobson) + + // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| + // (where x = 0.25 via Jacobson) + + static const int RTT_ESTIMATION_ALPHA_NUMERATOR = 8; + static const int RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR = 4; + + _rtt = (_rtt * (RTT_ESTIMATION_ALPHA_NUMERATOR - 1) + rtt) / RTT_ESTIMATION_ALPHA_NUMERATOR; + + _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR - 1) + + abs(rtt - _rtt)) / RTT_ESTIMATION_VARIANCE_ALPHA_NUMERATOR; +} + +int Connection::estimatedTimeout() const { + return _congestionControl->_userDefinedRTO ? _congestionControl->_rto : _rtt + _rttVariance * 4; +} + +void Connection::updateCongestionControlAndSendQueue(std::function congestionCallback) { + // update the last sent sequence number in congestion control + _congestionControl->setSendCurrentSequenceNumber(getSendQueue().getCurrentSequenceNumber()); + + // fire congestion control callback + congestionCallback(); + + auto& sendQueue = getSendQueue(); + + // now that we've updated the congestion control, update the packet send period and flow window size + sendQueue.setPacketSendPeriod(_congestionControl->_packetSendPeriod); + sendQueue.setEstimatedTimeout(estimatedTimeout()); + sendQueue.setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); + + // record connection stats + _stats.recordPacketSendPeriod(_congestionControl->_packetSendPeriod); + _stats.recordCongestionWindowSize(_congestionControl->_congestionWindowSize); +} + +void PendingReceivedMessage::enqueuePacket(std::unique_ptr packet) { + Q_ASSERT_X(packet->isPartOfMessage(), + "PendingReceivedMessage::enqueuePacket", + "called with a packet that is not part of a message"); + + if (_isComplete) { + qCDebug(networking) << "UNEXPECTED: Received packet for a message that is already complete"; + return; + } + + auto sequenceNumber = packet->getSequenceNumber(); + + if (packet->getPacketPosition() == Packet::PacketPosition::FIRST) { + _hasFirstSequenceNumber = true; + _firstSequenceNumber = sequenceNumber; + } else if (packet->getPacketPosition() == Packet::PacketPosition::LAST) { + _hasLastSequenceNumber = true; + _lastSequenceNumber = sequenceNumber; + } else if (packet->getPacketPosition() == Packet::PacketPosition::ONLY) { + _hasFirstSequenceNumber = true; + _hasLastSequenceNumber = true; + _firstSequenceNumber = sequenceNumber; + _lastSequenceNumber = sequenceNumber; + } + + // Insert into the packets list in sorted order. Because we generally expect to receive packets in order, begin + // searching from the end of the list. + auto it = find_if(_packets.rbegin(), _packets.rend(), + [&](const std::unique_ptr& packet) { return sequenceNumber > packet->getSequenceNumber(); }); + + _packets.insert(it.base(), std::move(packet)); + + if (_hasFirstSequenceNumber && _hasLastSequenceNumber) { + auto numPackets = udt::seqlen(_firstSequenceNumber, _lastSequenceNumber); + if (uint64_t(numPackets) == _packets.size()) { + _isComplete = true; + } + } +} diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h new file mode 100644 index 0000000000..f10c9dd720 --- /dev/null +++ b/libraries/networking/src/udt/Connection.h @@ -0,0 +1,165 @@ +// +// Connection.h +// libraries/networking/src/udt +// +// Created by Clement on 7/27/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 +// + +#ifndef hifi_Connection_h +#define hifi_Connection_h + +#include +#include +#include + +#include + +#include "ConnectionStats.h" +#include "Constants.h" +#include "LossList.h" +#include "PacketTimeWindow.h" +#include "SendQueue.h" +#include "../HifiSockAddr.h" + +namespace udt { + +class CongestionControl; +class ControlPacket; +class Packet; +class PacketList; +class Socket; + +class PendingReceivedMessage { +public: + void enqueuePacket(std::unique_ptr packet); + bool isComplete() const { return _isComplete; } + + std::list> _packets; + +private: + bool _isComplete { false }; + bool _hasFirstSequenceNumber { false }; + bool _hasLastSequenceNumber { false }; + SequenceNumber _firstSequenceNumber; + SequenceNumber _lastSequenceNumber; +}; + +class Connection : public QObject { + Q_OBJECT +public: + using SequenceNumberTimePair = std::pair; + using ACKListPair = std::pair; + using SentACKList = std::list; + + Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl); + ~Connection(); + + void sendReliablePacket(std::unique_ptr packet); + void sendReliablePacketList(std::unique_ptr packet); + + void sync(); // rate control method, fired by Socket for all connections on SYN interval + + // return indicates if this packet should be processed + bool processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize); + void processControl(std::unique_ptr controlPacket); + + void queueReceivedMessagePacket(std::unique_ptr packet); + + ConnectionStats::Stats sampleStats() { return _stats.sample(); } + +signals: + void packetSent(); + void connectionInactive(const HifiSockAddr& sockAddr); + +private slots: + void recordSentPackets(int payload, int total); + void recordRetransmission(); + void queueInactive(); + +private: + void sendACK(bool wasCausedBySyncTimeout = true); + void sendLightACK(); + void sendACK2(SequenceNumber currentACKSubSequenceNumber); + void sendNAK(SequenceNumber sequenceNumberRecieved); + void sendTimeoutNAK(); + + void processACK(std::unique_ptr controlPacket); + void processLightACK(std::unique_ptr controlPacket); + void processACK2(std::unique_ptr controlPacket); + void processNAK(std::unique_ptr controlPacket); + void processTimeoutNAK(std::unique_ptr controlPacket); + void processHandshake(std::unique_ptr controlPacket); + void processHandshakeACK(std::unique_ptr controlPacket); + void processProbeTail(std::unique_ptr controlPacket); + + void resetReceiveState(); + void resetRTT(); + + SendQueue& getSendQueue(); + SequenceNumber nextACK() const; + void updateRTT(int rtt); + + int estimatedTimeout() const; + + void updateCongestionControlAndSendQueue(std::function congestionCallback); + + void stopSendQueue(); + + int _synInterval; // Periodical Rate Control Interval, in microseconds + + int _nakInterval { -1 }; // NAK timeout interval, in microseconds, set on loss + int _minNAKInterval { 100000 }; // NAK timeout interval lower bound, default of 100ms + std::chrono::high_resolution_clock::time_point _lastNAKTime; + + bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server + bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client + + std::chrono::high_resolution_clock::time_point _connectionStart; // holds the time_point for creation of this connection + std::chrono::high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender + bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection + + LossList _lossList; // List of all missing packets + SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer + SequenceNumber _lastReceivedACK; // The last ACK received + SequenceNumber _lastReceivedAcknowledgedACK; // The last sent ACK that has been acknowledged via an ACK2 from the peer + SequenceNumber _currentACKSubSequenceNumber; // The current ACK sub-sequence number (used for Acknowledgment of ACKs) + + SequenceNumber _lastSentACK; // The last sent ACK + SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2 + + int _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN + int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval + + int32_t _rtt; // RTT, in microseconds + int32_t _rttVariance; // RTT variance + int _flowWindowSize { udt::MAX_PACKETS_IN_FLIGHT }; // Flow control window size + + int _bandwidth { 1 }; // Exponential moving average for estimated bandwidth, in packets per second + int _deliveryRate { 16 }; // Exponential moving average for receiver's receive rate, in packets per second + + SentACKList _sentACKs; // Map of ACK sub-sequence numbers to ACKed sequence number and sent time + + Socket* _parentSocket { nullptr }; + HifiSockAddr _destination; + + PacketTimeWindow _receiveWindow { 16, 64 }; // Window of interval between packets (16) and probes (64) for timing + bool _receivedControlProbeTail { false }; // Marker for receipt of control packet probe tail (in lieu of probe with data) + + std::unique_ptr _congestionControl; + + std::unique_ptr _sendQueue; + + std::map _pendingReceivedMessages; + + int _packetsSinceACK { 0 }; // The number of packets that have been received during the current ACK interval + + ConnectionStats _stats; +}; + +} + +#endif // hifi_Connection_h diff --git a/libraries/networking/src/udt/ConnectionStats.cpp b/libraries/networking/src/udt/ConnectionStats.cpp new file mode 100644 index 0000000000..52188c29c3 --- /dev/null +++ b/libraries/networking/src/udt/ConnectionStats.cpp @@ -0,0 +1,114 @@ +// +// ConnectionStats.cpp +// libraries/networking/src/udt +// +// Created by Clement on 7/29/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 "ConnectionStats.h" + +using namespace udt; +using namespace std::chrono; + +ConnectionStats::ConnectionStats() { + auto now = duration_cast(high_resolution_clock::now().time_since_epoch()); + _currentSample.startTime = now; + _total.startTime = now; +} + +ConnectionStats::Stats ConnectionStats::sample() { + Stats sample = _currentSample; + _currentSample = Stats(); + + auto now = duration_cast(high_resolution_clock::now().time_since_epoch()); + sample.endTime = now; + _currentSample.startTime = now; + + return sample; +} + +void ConnectionStats::record(Stats::Event event) { + ++_currentSample.events[(int) event]; + ++_total.events[(int) event]; +} + +void ConnectionStats::recordSentPackets(int payload, int total) { + ++_currentSample.sentPackets; + ++_total.sentPackets; + + _currentSample.sentUtilBytes += payload; + _total.sentUtilBytes += payload; + + _currentSample.sentBytes += total; + _total.sentBytes += total; +} + +void ConnectionStats::recordReceivedPackets(int payload, int total) { + ++_currentSample.receivedPackets; + ++_total.receivedPackets; + + _currentSample.receivedUtilBytes += payload; + _total.receivedUtilBytes += payload; + + _currentSample.receivedBytes += total; + _total.receivedBytes += total; +} + +void ConnectionStats::recordUnreliableSentPackets(int payload, int total) { + ++_currentSample.sentUnreliablePackets; + ++_total.sentUnreliablePackets; + + _currentSample.sentUnreliableUtilBytes += payload; + _total.sentUnreliableUtilBytes += payload; + + _currentSample.sentUnreliableBytes += total; + _total.sentUnreliableBytes += total; +} + +void ConnectionStats::recordUnreliableReceivedPackets(int payload, int total) { + ++_currentSample.receivedUnreliablePackets; + ++_total.receivedUnreliablePackets; + + _currentSample.receivedUnreliableUtilBytes += payload; + _total.receivedUnreliableUtilBytes += payload; + + _currentSample.sentUnreliableBytes += total; + _total.receivedUnreliableBytes += total; +} + +static const double EWMA_CURRENT_SAMPLE_WEIGHT = 0.125; +static const double EWMA_PREVIOUS_SAMPLES_WEIGHT = 1.0 - EWMA_CURRENT_SAMPLE_WEIGHT; + +void ConnectionStats::recordSendRate(int sample) { + _currentSample.sendRate = sample; + _total.sendRate = (_total.sendRate * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT); +} + +void ConnectionStats::recordReceiveRate(int sample) { + _currentSample.receiveRate = sample; + _total.receiveRate = (_total.receiveRate * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT); +} + +void ConnectionStats::recordEstimatedBandwidth(int sample) { + _currentSample.estimatedBandwith = sample; + _total.estimatedBandwith = (_total.estimatedBandwith * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT); +} + +void ConnectionStats::recordRTT(int sample) { + _currentSample.rtt = sample; + _total.rtt = (_total.rtt * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT); +} + +void ConnectionStats::recordCongestionWindowSize(int sample) { + _currentSample.congestionWindowSize = sample; + _total.congestionWindowSize = (_total.congestionWindowSize * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT); +} + +void ConnectionStats::recordPacketSendPeriod(int sample) { + _currentSample.packetSendPeriod = sample; + _total.packetSendPeriod = (_total.packetSendPeriod * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT); +} diff --git a/libraries/networking/src/udt/ConnectionStats.h b/libraries/networking/src/udt/ConnectionStats.h new file mode 100644 index 0000000000..494d433bca --- /dev/null +++ b/libraries/networking/src/udt/ConnectionStats.h @@ -0,0 +1,96 @@ +// +// ConnectionStats.h +// libraries/networking/src/udt +// +// Created by Clement on 7/29/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 +// + +#ifndef hifi_ConnectionStats_h +#define hifi_ConnectionStats_h + +#include +#include + +namespace udt { + +class ConnectionStats { +public: + struct Stats { + std::chrono::microseconds startTime; + std::chrono::microseconds endTime; + + enum Event { + SentACK, + ReceivedACK, + ProcessedACK, + SentLightACK, + ReceivedLightACK, + SentACK2, + ReceivedACK2, + SentNAK, + ReceivedNAK, + SentTimeoutNAK, + ReceivedTimeoutNAK, + Retransmission, + Duplicate + }; + + // construct a vector for the events of the size of our Enum - default value is zero + std::vector events = std::vector((int) Event::Duplicate + 1, 0); + + // packet counts and sizes + int sentPackets { 0 }; + int receivedPackets { 0 }; + int sentUtilBytes { 0 }; + int receivedUtilBytes { 0 }; + int sentBytes { 0 }; + int receivedBytes { 0 }; + + int sentUnreliablePackets { 0 }; + int receivedUnreliablePackets { 0 }; + int sentUnreliableUtilBytes { 0 }; + int receivedUnreliableUtilBytes { 0 }; + int sentUnreliableBytes { 0 }; + int receivedUnreliableBytes { 0 }; + + // the following stats are trailing averages in the result, not totals + int sendRate { 0 }; + int receiveRate { 0 }; + int estimatedBandwith { 0 }; + int rtt { 0 }; + int congestionWindowSize { 0 }; + int packetSendPeriod { 0 }; + }; + + ConnectionStats(); + + Stats sample(); + Stats getTotalStats(); + + void record(Stats::Event event); + + void recordSentPackets(int payload, int total); + void recordReceivedPackets(int payload, int total); + + void recordUnreliableSentPackets(int payload, int total); + void recordUnreliableReceivedPackets(int payload, int total); + + void recordSendRate(int sample); + void recordReceiveRate(int sample); + void recordEstimatedBandwidth(int sample); + void recordRTT(int sample); + void recordCongestionWindowSize(int sample); + void recordPacketSendPeriod(int sample); + +private: + Stats _currentSample; + Stats _total; +}; + +} + +#endif // hifi_ConnectionStats_h diff --git a/libraries/networking/src/udt/Constants.h b/libraries/networking/src/udt/Constants.h new file mode 100644 index 0000000000..0152444f84 --- /dev/null +++ b/libraries/networking/src/udt/Constants.h @@ -0,0 +1,34 @@ +// +// Constants.h +// libraries/networking/src/udt +// +// Created by Clement on 7/13/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 +// + +#pragma once + +#ifndef hifi_udt_Constants_h +#define hifi_udt_Constants_h + +#include "SequenceNumber.h" + +namespace udt { + static const int MAX_PACKET_SIZE_WITH_UDP_HEADER = 1500; + static const int MAX_PACKET_SIZE = MAX_PACKET_SIZE_WITH_UDP_HEADER - 28; + static const int MAX_PACKETS_IN_FLIGHT = 25600; + static const int CONNECTION_RECEIVE_BUFFER_SIZE_PACKETS = 8192; + static const int CONNECTION_SEND_BUFFER_SIZE_PACKETS = 8192; + static const int UDP_SEND_BUFFER_SIZE_BYTES = 1048576; + static const int UDP_RECEIVE_BUFFER_SIZE_BYTES = 1048576; + static const int DEFAULT_SYN_INTERVAL_USECS = 10 * 1000; + static const int SEQUENCE_NUMBER_BITS = sizeof(SequenceNumber) * 8; + static const int MESSAGE_LINE_NUMBER_BITS = 32; + static const int MESSAGE_NUMBER_BITS = 30; + static const uint32_t CONTROL_BIT_MASK = uint32_t(1) << (SEQUENCE_NUMBER_BITS - 1); +} + +#endif // hifi_udt_Constants_h diff --git a/libraries/networking/src/udt/ControlPacket.cpp b/libraries/networking/src/udt/ControlPacket.cpp new file mode 100644 index 0000000000..e672ab426b --- /dev/null +++ b/libraries/networking/src/udt/ControlPacket.cpp @@ -0,0 +1,107 @@ +// +// ControlPacket.cpp +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-24. +// 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 "ControlPacket.h" + +#include "Constants.h" + +using namespace udt; + +int ControlPacket::localHeaderSize() { + return sizeof(ControlPacket::ControlBitAndType); +} +int ControlPacket::totalHeaderSize() { + return BasePacket::totalHeaderSize() + ControlPacket::localHeaderSize(); +} +int ControlPacket::maxPayloadSize() { + return BasePacket::maxPayloadSize() - ControlPacket::localHeaderSize(); +} + +std::unique_ptr ControlPacket::fromReceivedPacket(std::unique_ptr data, qint64 size, + const HifiSockAddr &senderSockAddr) { + // Fail with null data + Q_ASSERT(data); + + // Fail with invalid size + Q_ASSERT(size >= 0); + + // allocate memory + auto packet = std::unique_ptr(new ControlPacket(std::move(data), size, senderSockAddr)); + + packet->open(QIODevice::ReadOnly); + + return packet; +} + +std::unique_ptr ControlPacket::create(Type type, qint64 size) { + return std::unique_ptr(new ControlPacket(type, size)); +} + +ControlPacket::ControlPacket(Type type, qint64 size) : + BasePacket((size == -1) ? -1 : ControlPacket::localHeaderSize() + size), + _type(type) +{ + adjustPayloadStartAndCapacity(ControlPacket::localHeaderSize()); + + open(QIODevice::ReadWrite); + + writeType(); +} + +ControlPacket::ControlPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr) : + BasePacket(std::move(data), size, senderSockAddr) +{ + // sanity check before we decrease the payloadSize with the payloadCapacity + Q_ASSERT(_payloadSize == _payloadCapacity); + + adjustPayloadStartAndCapacity(ControlPacket::localHeaderSize(), _payloadSize > 0); + + readType(); +} + +ControlPacket::ControlPacket(ControlPacket&& other) : + BasePacket(std::move(other)) +{ + _type = other._type; +} + +ControlPacket& ControlPacket::operator=(ControlPacket&& other) { + BasePacket::operator=(std::move(other)); + + _type = other._type; + + return *this; +} + +void ControlPacket::setType(udt::ControlPacket::Type type) { + _type = type; + + writeType(); +} + +void ControlPacket::writeType() { + ControlBitAndType* bitAndType = reinterpret_cast(_packet.get()); + + // We override the control bit here by writing the type but it's okay, it'll always be 1 + *bitAndType = CONTROL_BIT_MASK | (ControlBitAndType(_type) << (8 * sizeof(Type))); +} + +void ControlPacket::readType() { + ControlBitAndType bitAndType = *reinterpret_cast(_packet.get()); + + Q_ASSERT_X(bitAndType & CONTROL_BIT_MASK, "ControlPacket::readHeader()", "This should be a control packet"); + + uint16_t packetType = (bitAndType & ~CONTROL_BIT_MASK) >> (8 * sizeof(Type)); + Q_ASSERT_X(packetType <= ControlPacket::Type::ProbeTail, "ControlPacket::readType()", "Received a control packet with wrong type"); + + // read the type + _type = (Type) packetType; +} diff --git a/libraries/networking/src/udt/ControlPacket.h b/libraries/networking/src/udt/ControlPacket.h new file mode 100644 index 0000000000..fea8210ba0 --- /dev/null +++ b/libraries/networking/src/udt/ControlPacket.h @@ -0,0 +1,72 @@ +// +// ControlPacket.h +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-24. +// 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 +// + +#pragma once + +#ifndef hifi_ControlPacket_h +#define hifi_ControlPacket_h + +#include + +#include "BasePacket.h" +#include "Packet.h" + +namespace udt { + +class ControlPacket : public BasePacket { + Q_OBJECT +public: + using ControlBitAndType = uint32_t; + + enum Type : uint16_t { + ACK, + ACK2, + LightACK, + NAK, + TimeoutNAK, + Handshake, + HandshakeACK, + ProbeTail + }; + + static std::unique_ptr create(Type type, qint64 size = -1); + static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, + const HifiSockAddr& senderSockAddr); + // Current level's header size + static int localHeaderSize(); + // Cumulated size of all the headers + static int totalHeaderSize(); + // The maximum payload size this packet can use to fit in MTU + static int maxPayloadSize(); + + Type getType() const { return _type; } + void setType(Type type); + +private: + ControlPacket(Type type, qint64 size = -1); + ControlPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); + ControlPacket(ControlPacket&& other); + ControlPacket(const ControlPacket& other) = delete; + + ControlPacket& operator=(ControlPacket&& other); + ControlPacket& operator=(const ControlPacket& other) = delete; + + // Header read/write + void readType(); + void writeType(); + + Type _type; +}; + +} // namespace udt + + +#endif // hifi_ControlPacket_h diff --git a/libraries/networking/src/udt/LossList.cpp b/libraries/networking/src/udt/LossList.cpp new file mode 100644 index 0000000000..846347142e --- /dev/null +++ b/libraries/networking/src/udt/LossList.cpp @@ -0,0 +1,184 @@ +// +// LossList.cpp +// libraries/networking/src/udt +// +// Created by Clement on 7/27/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 "LossList.h" + +#include "ControlPacket.h" + +using namespace udt; +using namespace std; + +void LossList::append(SequenceNumber seq) { + Q_ASSERT_X(_lossList.empty() || (_lossList.back().second < seq), "LossList::append(SequenceNumber)", + "SequenceNumber appended is not greater than the last SequenceNumber in the list"); + + if (getLength() > 0 && _lossList.back().second + 1 == seq) { + ++_lossList.back().second; + } else { + _lossList.push_back(make_pair(seq, seq)); + } + _length += 1; +} + +void LossList::append(SequenceNumber start, SequenceNumber end) { + Q_ASSERT_X(_lossList.empty() || (_lossList.back().second < start), + "LossList::append(SequenceNumber, SequenceNumber)", + "SequenceNumber range appended is not greater than the last SequenceNumber in the list"); + Q_ASSERT_X(start <= end, + "LossList::append(SequenceNumber, SequenceNumber)", "Range start greater than range end"); + + if (getLength() > 0 && _lossList.back().second + 1 == start) { + _lossList.back().second = end; + } else { + _lossList.push_back(make_pair(start, end)); + } + _length += seqlen(start, end); +} + +void LossList::insert(SequenceNumber start, SequenceNumber end) { + Q_ASSERT_X(start <= end, + "LossList::insert(SequenceNumber, SequenceNumber)", "Range start greater than range end"); + + auto it = find_if_not(_lossList.begin(), _lossList.end(), [&start](pair pair){ + return pair.second < start; + }); + + if (it == _lossList.end() || end < it->first) { + // No overlap, simply insert + _length += seqlen(start, end); + _lossList.insert(it, make_pair(start, end)); + } else { + // If it starts before segment, extend segment + if (start < it->first) { + _length += seqlen(start, it->first - 1); + it->first = start; + } + + // If it ends after segment, extend segment + if (end > it->second) { + _length += seqlen(it->second + 1, end); + it->second = end; + } + + auto it2 = it; + ++it2; + // For all ranges touching the current range + while (it2 != _lossList.end() && it->second >= it2->first - 1) { + // extend current range if necessary + if (it->second < it2->second) { + _length += seqlen(it->second + 1, it2->second); + it->second = it2->second; + } + + // Remove overlapping range + _length -= seqlen(it2->first, it2->second); + it2 = _lossList.erase(it2); + } + } +} + +bool LossList::remove(SequenceNumber seq) { + auto it = find_if(_lossList.begin(), _lossList.end(), [&seq](pair pair) { + return pair.first <= seq && seq <= pair.second; + }); + + if (it != end(_lossList)) { + if (it->first == it->second) { + _lossList.erase(it); + } else if (seq == it->first) { + ++it->first; + } else if (seq == it->second) { + --it->second; + } else { + auto temp = it->second; + it->second = seq - 1; + _lossList.insert(++it, make_pair(seq + 1, temp)); + } + _length -= 1; + + // this sequence number was found in the loss list, return true + return true; + } else { + // this sequence number was not found in the loss list, return false + return false; + } +} + +void LossList::remove(SequenceNumber start, SequenceNumber end) { + Q_ASSERT_X(start <= end, + "LossList::remove(SequenceNumber, SequenceNumber)", "Range start greater than range end"); + // Find the first segment sharing sequence numbers + auto it = find_if(_lossList.begin(), _lossList.end(), [&start, &end](pair pair) { + return (pair.first <= start && start <= pair.second) || (start <= pair.first && pair.first <= end); + }); + + // If we found one + if (it != _lossList.end()) { + + // While the end of the current segment is contained, either shorten it (first one only - sometimes) + // or remove it altogether since it is fully contained it the range + while (it != _lossList.end() && end >= it->second) { + if (start <= it->first) { + // Segment is contained, update new length and erase it. + _length -= seqlen(it->first, it->second); + it = _lossList.erase(it); + } else { + // Beginning of segment not contained, modify end of segment. + // Will only occur sometimes one the first loop + _length -= seqlen(start, it->second); + it->second = start - 1; + ++it; + } + } + + // There might be more to remove + if (it != _lossList.end() && it->first <= end) { + if (start <= it->first) { + // Truncate beginning of segment + _length -= seqlen(it->first, end); + it->first = end + 1; + } else { + // Cut it in half if the range we are removing is contained within one segment + _length -= seqlen(start, end); + auto temp = it->second; + it->second = start - 1; + _lossList.insert(++it, make_pair(end + 1, temp)); + } + } + } +} + +SequenceNumber LossList::getFirstSequenceNumber() const { + Q_ASSERT_X(getLength() > 0, "LossList::getFirstSequenceNumber()", "Trying to get first element of an empty list"); + return _lossList.front().first; +} + +SequenceNumber LossList::popFirstSequenceNumber() { + auto front = getFirstSequenceNumber(); + remove(front); + return front; +} + +void LossList::write(ControlPacket& packet, int maxPairs) { + int writtenPairs = 0; + + for (const auto& pair : _lossList) { + packet.writePrimitive(pair.first); + packet.writePrimitive(pair.second); + + ++writtenPairs; + + // check if we've written the maximum number we were told to write + if (maxPairs != -1 && writtenPairs >= maxPairs) { + break; + } + } +} diff --git a/libraries/networking/src/udt/LossList.h b/libraries/networking/src/udt/LossList.h new file mode 100644 index 0000000000..f0f2b92988 --- /dev/null +++ b/libraries/networking/src/udt/LossList.h @@ -0,0 +1,52 @@ +// +// LossList.h +// libraries/networking/src/udt +// +// Created by Clement on 7/27/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 +// + +#ifndef hifi_LossList_h +#define hifi_LossList_h + +#include + +#include "SequenceNumber.h" + +namespace udt { + +class ControlPacket; + +class LossList { +public: + LossList() {} + + void clear() { _length = 0; _lossList.clear(); } + + // must always add at the end - faster than insert + void append(SequenceNumber seq); + void append(SequenceNumber start, SequenceNumber end); + + // inserts anywhere - MUCH slower + void insert(SequenceNumber start, SequenceNumber end); + + bool remove(SequenceNumber seq); + void remove(SequenceNumber start, SequenceNumber end); + + int getLength() const { return _length; } + SequenceNumber getFirstSequenceNumber() const; + SequenceNumber popFirstSequenceNumber(); + + void write(ControlPacket& packet, int maxPairs = -1); + +private: + std::list> _lossList; + int _length { 0 }; +}; + +} + +#endif // hifi_LossList_h diff --git a/libraries/networking/src/udt/Packet.cpp b/libraries/networking/src/udt/Packet.cpp index 81747749be..56c65e0657 100644 --- a/libraries/networking/src/udt/Packet.cpp +++ b/libraries/networking/src/udt/Packet.cpp @@ -11,20 +11,24 @@ #include "Packet.h" -const qint64 Packet::PACKET_WRITE_ERROR = -1; +using namespace udt; -qint64 Packet::localHeaderSize(PacketType::Value type) { - qint64 size = numBytesForArithmeticCodedPacketType(type) + sizeof(PacketVersion) + - ((SEQUENCE_NUMBERED_PACKETS.contains(type)) ? sizeof(SequenceNumber) : 0); - return size; +int Packet::localHeaderSize(bool isPartOfMessage) { + return sizeof(Packet::SequenceNumberAndBitField) + + (isPartOfMessage ? sizeof(Packet::MessageNumberAndBitField) : 0); } -qint64 Packet::maxPayloadSize(PacketType::Value type) { - return MAX_PACKET_SIZE - localHeaderSize(type); +int Packet::totalHeaderSize(bool isPartOfMessage) { + return BasePacket::totalHeaderSize() + Packet::localHeaderSize(isPartOfMessage); } -std::unique_ptr Packet::create(PacketType::Value type, qint64 size) { - auto packet = std::unique_ptr(new Packet(type, size)); +int Packet::maxPayloadSize(bool isPartOfMessage) { + return BasePacket::maxPayloadSize() - Packet::localHeaderSize(isPartOfMessage); +} + + +std::unique_ptr Packet::create(qint64 size, bool isReliable, bool isPartOfMessage) { + auto packet = std::unique_ptr(new Packet(size, isReliable, isPartOfMessage)); packet->open(QIODevice::ReadWrite); @@ -47,217 +51,119 @@ std::unique_ptr Packet::createCopy(const Packet& other) { return std::unique_ptr(new Packet(other)); } -qint64 Packet::totalHeadersSize() const { - return localHeaderSize(); -} - -qint64 Packet::localHeaderSize() const { - return localHeaderSize(_type); -} - -Packet::Packet(PacketType::Value type, qint64 size) : - _type(type), - _version(0) +Packet::Packet(qint64 size, bool isReliable, bool isPartOfMessage) : + BasePacket((size == -1) ? -1 : (Packet::localHeaderSize() + size)), + _isReliable(isReliable), + _isPartOfMessage(isPartOfMessage) { - auto maxPayload = maxPayloadSize(type); - if (size == -1) { - // default size of -1, means biggest packet possible - size = maxPayload; - } - - _packetSize = localHeaderSize(type) + size; - _packet.reset(new char[_packetSize]); - _payloadCapacity = size; - _payloadStart = _packet.get() + (_packetSize - _payloadCapacity); - - // Sanity check - Q_ASSERT(size >= 0 || size < maxPayload); - - // copy packet type and version in header - writePacketTypeAndVersion(type); - - // Set control bit and sequence number to 0 if necessary - if (SEQUENCE_NUMBERED_PACKETS.contains(type)) { - writeSequenceNumber(0); - } + adjustPayloadStartAndCapacity(Packet::localHeaderSize(_isPartOfMessage)); + // set the UDT header to default values + writeHeader(); } Packet::Packet(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr) : - _packetSize(size), - _packet(std::move(data)), - _senderSockAddr(senderSockAddr) + BasePacket(std::move(data), size, senderSockAddr) { - _type = readType(); - _version = readVersion(); - _payloadCapacity = _packetSize - localHeaderSize(_type); - _payloadSize = _payloadCapacity; - _payloadStart = _packet.get() + (_packetSize - _payloadCapacity); + readHeader(); + + adjustPayloadStartAndCapacity(Packet::localHeaderSize(_isPartOfMessage), _payloadSize > 0); } Packet::Packet(const Packet& other) : - QIODevice() + BasePacket(other) { - *this = other; - - if (other.isOpen()) { - this->open(other.openMode()); - } - - this->seek(other.pos()); + _isReliable = other._isReliable; + _isPartOfMessage = other._isPartOfMessage; + _sequenceNumber = other._sequenceNumber; } Packet& Packet::operator=(const Packet& other) { - _type = other._type; + BasePacket::operator=(other); - _packetSize = other._packetSize; - _packet = std::unique_ptr(new char[_packetSize]); - memcpy(_packet.get(), other._packet.get(), _packetSize); - - _payloadStart = _packet.get() + (other._payloadStart - other._packet.get()); - _payloadCapacity = other._payloadCapacity; - - _payloadSize = other._payloadSize; + _isReliable = other._isReliable; + _isPartOfMessage = other._isPartOfMessage; + _sequenceNumber = other._sequenceNumber; return *this; } -Packet::Packet(Packet&& other) { - *this = std::move(other); +Packet::Packet(Packet&& other) : + BasePacket(std::move(other)) +{ + _isReliable = other._isReliable; + _isPartOfMessage = other._isPartOfMessage; + _sequenceNumber = other._sequenceNumber; + _packetPosition = other._packetPosition; + _messageNumber = other._messageNumber; } Packet& Packet::operator=(Packet&& other) { - _type = other._type; + BasePacket::operator=(std::move(other)); - _packetSize = other._packetSize; - _packet = std::move(other._packet); - - _payloadStart = other._payloadStart; - _payloadCapacity = other._payloadCapacity; - - _payloadSize = other._payloadSize; + _isReliable = other._isReliable; + _isPartOfMessage = other._isPartOfMessage; + _sequenceNumber = other._sequenceNumber; + _packetPosition = other._packetPosition; + _messageNumber = other._messageNumber; return *this; } -void Packet::setPayloadSize(qint64 payloadSize) { - if (isWritable()) { - Q_ASSERT(payloadSize <= _payloadCapacity); - _payloadSize = payloadSize; - } else { - qDebug() << "You can not call setPayloadSize for a non-writeable Packet."; - Q_ASSERT(false); +void Packet::writeMessageNumber(MessageNumber messageNumber) { + _isPartOfMessage = true; + _messageNumber = messageNumber; + writeHeader(); +} + +void Packet::writeSequenceNumber(SequenceNumber sequenceNumber) const { + _sequenceNumber = sequenceNumber; + writeHeader(); +} + +static const uint32_t RELIABILITY_BIT_MASK = uint32_t(1) << (SEQUENCE_NUMBER_BITS - 2); +static const uint32_t MESSAGE_BIT_MASK = uint32_t(1) << (SEQUENCE_NUMBER_BITS - 3); +static const uint32_t BIT_FIELD_MASK = CONTROL_BIT_MASK | RELIABILITY_BIT_MASK | MESSAGE_BIT_MASK; + +static const uint32_t PACKET_POSITION_MASK = uint32_t(0x03) << 30; +static const uint32_t MESSAGE_NUMBER_MASK = ~PACKET_POSITION_MASK; + +void Packet::readHeader() const { + SequenceNumberAndBitField* seqNumBitField = reinterpret_cast(_packet.get()); + + Q_ASSERT_X(!(*seqNumBitField & CONTROL_BIT_MASK), "Packet::readHeader()", "This should be a data packet"); + + _isReliable = (bool) (*seqNumBitField & RELIABILITY_BIT_MASK); // Only keep reliability bit + _isPartOfMessage = (bool) (*seqNumBitField & MESSAGE_BIT_MASK); // Only keep message bit + _sequenceNumber = SequenceNumber{ *seqNumBitField & ~BIT_FIELD_MASK }; // Remove the bit field + + if (_isPartOfMessage) { + MessageNumberAndBitField* messageNumberAndBitField = seqNumBitField + 1; + _messageNumber = *messageNumberAndBitField & MESSAGE_NUMBER_MASK; + _packetPosition = static_cast(*messageNumberAndBitField >> 30); } } -bool Packet::reset() { - if (isWritable()) { - _payloadSize = 0; +void Packet::writeHeader() const { + // grab pointer to current SequenceNumberAndBitField + SequenceNumberAndBitField* seqNumBitField = reinterpret_cast(_packet.get()); + + // Write sequence number and reset bit field + Q_ASSERT_X(!((SequenceNumber::Type)_sequenceNumber & BIT_FIELD_MASK), + "Packet::writeHeader()", "Sequence number is overflowing into bit field"); + *seqNumBitField = ((SequenceNumber::Type)_sequenceNumber); + + if (_isReliable) { + *seqNumBitField |= RELIABILITY_BIT_MASK; } - return QIODevice::reset(); -} + if (_isPartOfMessage) { + *seqNumBitField |= MESSAGE_BIT_MASK; -void Packet::setType(PacketType::Value type) { - auto currentHeaderSize = totalHeadersSize(); - _type = type; - writePacketTypeAndVersion(_type); + Q_ASSERT_X(!(_messageNumber & PACKET_POSITION_MASK), + "Packet::writeHeader()", "Message number is overflowing into bit field"); - // Setting new packet type with a different header size not currently supported - Q_ASSERT(currentHeaderSize == totalHeadersSize()); - Q_UNUSED(currentHeaderSize); -} - -PacketType::Value Packet::readType() const { - return (PacketType::Value)arithmeticCodingValueFromBuffer(_packet.get()); -} - -PacketVersion Packet::readVersion() const { - return *reinterpret_cast(_packet.get() + numBytesForArithmeticCodedPacketType(_type)); -} - -Packet::SequenceNumber Packet::readSequenceNumber() const { - if (SEQUENCE_NUMBERED_PACKETS.contains(_type)) { - SequenceNumber seqNum = *reinterpret_cast(_packet.get() + - numBytesForArithmeticCodedPacketType(_type) + - sizeof(PacketVersion)); - return seqNum & ~(1 << 15); // remove control bit - } - return -1; -} - -bool Packet::readIsControlPacket() const { - if (SEQUENCE_NUMBERED_PACKETS.contains(_type)) { - SequenceNumber seqNum = *reinterpret_cast(_packet.get() + - numBytesForArithmeticCodedPacketType(_type) + - sizeof(PacketVersion)); - return seqNum & (1 << 15); // Only keep control bit - } - return false; -} - -void Packet::writePacketTypeAndVersion(PacketType::Value type) { - // Pack the packet type - auto offset = packArithmeticallyCodedValue(type, _packet.get()); - - // Pack the packet version - auto version = versionForPacketType(type); - memcpy(_packet.get() + offset, &version, sizeof(version)); -} - -void Packet::writeSequenceNumber(SequenceNumber seqNum) { - // Here we are overriding the control bit to 0. - // But that is not an issue since we should only ever set the seqNum - // for data packets going out - memcpy(_packet.get() + numBytesForArithmeticCodedPacketType(_type) + sizeof(PacketVersion), - &seqNum, sizeof(seqNum)); -} - -QByteArray Packet::read(qint64 maxSize) { - qint64 sizeToRead = std::min(size() - pos(), maxSize); - QByteArray data { getPayload() + pos(), (int) sizeToRead }; - seek(pos() + sizeToRead); - return data; -} - -QByteArray Packet::readWithoutCopy(qint64 maxSize) { - qint64 sizeToRead = std::min(size() - pos(), maxSize); - QByteArray data { QByteArray::fromRawData(getPayload() + pos(), sizeToRead) }; - seek(pos() + sizeToRead); - return data; -} - -qint64 Packet::writeData(const char* data, qint64 maxSize) { - - // make sure we have the space required to write this block - if (maxSize <= bytesAvailableForWrite()) { - qint64 currentPos = pos(); - - Q_ASSERT(currentPos < _payloadCapacity); - - // good to go - write the data - memcpy(_payloadStart + currentPos, data, maxSize); - - // keep track of _payloadSize so we can just write the actual data when packet is about to be sent - _payloadSize = std::max(currentPos + maxSize, _payloadSize); - - // return the number of bytes written - return maxSize; - } else { - // not enough space left for this write - return an error - return PACKET_WRITE_ERROR; + MessageNumberAndBitField* messageNumberAndBitField = seqNumBitField + 1; + *messageNumberAndBitField = _messageNumber; + *messageNumberAndBitField |= _packetPosition << 30; } } - -qint64 Packet::readData(char* dest, qint64 maxSize) { - // we're either reading what is left from the current position or what was asked to be read - qint64 numBytesToRead = std::min(bytesLeftToRead(), maxSize); - - if (numBytesToRead > 0) { - int currentPosition = pos(); - - // read out the data - memcpy(dest, _payloadStart + currentPosition, numBytesToRead); - } - - return numBytesToRead; -} diff --git a/libraries/networking/src/udt/Packet.h b/libraries/networking/src/udt/Packet.h index 569ff4b9a4..565fc24616 100644 --- a/libraries/networking/src/udt/Packet.h +++ b/libraries/networking/src/udt/Packet.h @@ -1,6 +1,6 @@ // // Packet.h -// libraries/networking/src +// libraries/networking/src/udt // // Created by Clement on 7/2/15. // Copyright 2015 High Fidelity, Inc. @@ -16,121 +16,78 @@ #include -#include "../HifiSockAddr.h" +#include "BasePacket.h" #include "PacketHeaders.h" +#include "SequenceNumber.h" -class Packet : public QIODevice { +namespace udt { + +class Packet : public BasePacket { Q_OBJECT public: - using SequenceNumber = uint16_t; + // NOTE: The SequenceNumber is only actually 29 bits to leave room for a bit field + using SequenceNumberAndBitField = uint32_t; + + // NOTE: The MessageNumber is only actually 29 bits to leave room for a bit field + using MessageNumber = uint32_t; + using MessageNumberAndBitField = uint32_t; - static const qint64 PACKET_WRITE_ERROR; - - static std::unique_ptr create(PacketType::Value type, qint64 size = -1); + // Use same size as MessageNumberAndBitField so we can use the enum with bitwise operations + enum PacketPosition : MessageNumberAndBitField { + ONLY = 0x0, + FIRST = 0x2, + MIDDLE = 0x3, + LAST = 0x1 + }; + + static std::unique_ptr create(qint64 size = -1, bool isReliable = false, bool isPartOfMessage = false); static std::unique_ptr fromReceivedPacket(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); // Provided for convenience, try to limit use static std::unique_ptr createCopy(const Packet& other); - - static qint64 localHeaderSize(PacketType::Value type); - static qint64 maxPayloadSize(PacketType::Value type); - - virtual qint64 totalHeadersSize() const; // Cumulated size of all the headers - virtual qint64 localHeaderSize() const; // Current level's header size - - // Payload direct access to the payload, use responsibly! - char* getPayload() { return _payloadStart; } - const char* getPayload() const { return _payloadStart; } - - // Return direct access to the entire packet, use responsibly! - char* getData() { return _packet.get(); } - const char* getData() const { return _packet.get(); } - - PacketType::Value getType() const { return _type; } - void setType(PacketType::Value type); - PacketVersion getVersion() const { return _version; } + // Current level's header size + static int localHeaderSize(bool isPartOfMessage = false); + // Cumulated size of all the headers + static int totalHeaderSize(bool isPartOfMessage = false); + // The maximum payload size this packet can use to fit in MTU + static int maxPayloadSize(bool isPartOfMessage = false); - // Returns the size of the packet, including the header - qint64 getDataSize() const { return totalHeadersSize() + _payloadSize; } - - // Returns the size of the payload only - qint64 getPayloadSize() const { return _payloadSize; } - - // Allows a writer to change the size of the payload used when writing directly - void setPayloadSize(qint64 payloadSize); - - // Returns the number of bytes allocated for the payload - qint64 getPayloadCapacity() const { return _payloadCapacity; } + bool isPartOfMessage() const { return _isPartOfMessage; } + bool isReliable() const { return _isReliable; } + SequenceNumber getSequenceNumber() const { return _sequenceNumber; } - qint64 bytesLeftToRead() const { return _payloadSize - pos(); } - qint64 bytesAvailableForWrite() const { return _payloadCapacity - pos(); } + MessageNumber getMessageNumber() const { return _messageNumber; } - HifiSockAddr& getSenderSockAddr() { return _senderSockAddr; } - const HifiSockAddr& getSenderSockAddr() const { return _senderSockAddr; } + void setPacketPosition(PacketPosition position) { _packetPosition = position; } + PacketPosition getPacketPosition() const { return _packetPosition; } - void writeSequenceNumber(SequenceNumber seqNum); - SequenceNumber readSequenceNumber() const; - bool readIsControlPacket() const; - - // QIODevice virtual functions - // WARNING: Those methods all refer to the payload ONLY and NOT the entire packet - virtual bool isSequential() const { return false; } - virtual bool reset(); - virtual qint64 size() const { return _payloadCapacity; } - - using QIODevice::read; - QByteArray read(qint64 maxSize); - QByteArray readWithoutCopy(qint64 maxSize); // this can only be used if packet will stay in scope - - template qint64 peekPrimitive(T* data); - template qint64 readPrimitive(T* data); - template qint64 writePrimitive(const T& data); + void writeMessageNumber(MessageNumber messageNumber); + void writeSequenceNumber(SequenceNumber sequenceNumber) const; protected: - Packet(PacketType::Value type, qint64 size); + Packet(qint64 size, bool isReliable = false, bool isPartOfMessage = false); Packet(std::unique_ptr data, qint64 size, const HifiSockAddr& senderSockAddr); + Packet(const Packet& other); - Packet& operator=(const Packet& other); Packet(Packet&& other); + + Packet& operator=(const Packet& other); Packet& operator=(Packet&& other); - // Header readers - PacketType::Value readType() const; - PacketVersion readVersion() const; +private: + // Header readers - these read data to member variables after pulling packet off wire + void readHeader() const; + void writeHeader() const; - // QIODevice virtual functions - virtual qint64 writeData(const char* data, qint64 maxSize); - virtual qint64 readData(char* data, qint64 maxSize); - - // Header writers - void writePacketTypeAndVersion(PacketType::Value type); - - PacketType::Value _type; // Packet type - PacketVersion _version; // Packet version - - qint64 _packetSize = 0; // Total size of the allocated memory - std::unique_ptr _packet; // Allocated memory - - char* _payloadStart = nullptr; // Start of the payload - qint64 _payloadCapacity = 0; // Total capacity of the payload - - qint64 _payloadSize = 0; // How much of the payload is actually used - - HifiSockAddr _senderSockAddr; // sender address for packet (only used on receiving end) + // Simple holders to prevent multiple reading and bitwise ops + mutable bool _isReliable { false }; + mutable bool _isPartOfMessage { false }; + mutable SequenceNumber _sequenceNumber; + mutable PacketPosition _packetPosition { PacketPosition::ONLY }; + mutable MessageNumber _messageNumber { 0 }; }; - -template qint64 Packet::peekPrimitive(T* data) { - return QIODevice::peek(reinterpret_cast(data), sizeof(T)); -} - -template qint64 Packet::readPrimitive(T* data) { - return QIODevice::read(reinterpret_cast(data), sizeof(T)); -} - -template qint64 Packet::writePrimitive(const T& data) { - static_assert(!std::is_pointer::value, "T must not be a pointer"); - return QIODevice::write(reinterpret_cast(&data), sizeof(T)); -} + +} // namespace udt #endif // hifi_Packet_h diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index bb120ce198..7e00acfdc0 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -15,119 +15,95 @@ #include -using namespace PacketType; +const QSet NON_VERIFIED_PACKETS = QSet() + << PacketType::NodeJsonStats << PacketType::EntityQuery + << PacketType::OctreeDataNack << PacketType::EntityEditNack + << PacketType::DomainListRequest << PacketType::StopNode; -const QSet NON_VERIFIED_PACKETS = QSet() - << NodeJsonStats << EntityQuery - << OctreeDataNack << EntityEditNack - << DomainListRequest << StopNode; +const QSet NON_SOURCED_PACKETS = QSet() + << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment + << PacketType::DomainServerRequireDTLS << PacketType::DomainConnectRequest + << PacketType::DomainList << PacketType::DomainConnectionDenied + << PacketType::DomainServerPathQuery << PacketType::DomainServerPathResponse + << PacketType::DomainServerAddedNode << PacketType::DomainServerConnectionToken + << PacketType::DomainSettingsRequest << PacketType::DomainSettings + << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat + << PacketType::ICEPing << PacketType::ICEPingReply + << PacketType::AssignmentClientStatus << PacketType::StopNode; -const QSet SEQUENCE_NUMBERED_PACKETS = QSet() << AvatarData; +const QSet RELIABLE_PACKETS = QSet(); -const QSet NON_SOURCED_PACKETS = QSet() - << StunResponse << CreateAssignment << RequestAssignment - << DomainServerRequireDTLS << DomainConnectRequest << DomainServerConnectionToken - << DomainList << DomainConnectionDenied - << DomainServerPathQuery << DomainServerPathResponse - << DomainServerAddedNode - << ICEServerPeerInformation << ICEServerQuery << ICEServerHeartbeat - << ICEPing << ICEPingReply - << AssignmentClientStatus << StopNode; - -int arithmeticCodingValueFromBuffer(const char* checkValue) { - if (((uchar) *checkValue) < 255) { - return *checkValue; - } else { - return 255 + arithmeticCodingValueFromBuffer(checkValue + 1); - } -} - -int numBytesArithmeticCodingFromBuffer(const char* checkValue) { - if (((uchar) *checkValue) < 255) { - return 1; - } else { - return 1 + numBytesArithmeticCodingFromBuffer(checkValue + 1); - } -} - -int packArithmeticallyCodedValue(int value, char* destination) { - if (value < 255) { - // less than 255, just pack our value - destination[0] = (uchar) value; - return 1; - } else { - // pack 255 and then recursively pack on - ((unsigned char*)destination)[0] = 255; - return 1 + packArithmeticallyCodedValue(value - 255, destination + 1); - } -} - -PacketVersion versionForPacketType(PacketType::Value packetType) { +PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { - case EntityAdd: - case EntityEdit: - case EntityData: - return VERSION_ENTITIES_PARTICLE_COLOR_PROPERTIES; - case AvatarData: - return 13; + case PacketType::EntityAdd: + case PacketType::EntityEdit: + case PacketType::EntityData: + return VERSION_ENTITIES_PROTOCOL_HEADER_SWAP; default: - return 11; + return 14; } } #define PACKET_TYPE_NAME_LOOKUP(x) case x: return QString(#x); -QString nameForPacketType(PacketType::Value packetType) { +QString nameForPacketType(PacketType packetType) { switch (packetType) { - PACKET_TYPE_NAME_LOOKUP(Unknown); - PACKET_TYPE_NAME_LOOKUP(StunResponse); - PACKET_TYPE_NAME_LOOKUP(DomainList); - PACKET_TYPE_NAME_LOOKUP(Ping); - PACKET_TYPE_NAME_LOOKUP(PingReply); - PACKET_TYPE_NAME_LOOKUP(KillAvatar); - PACKET_TYPE_NAME_LOOKUP(AvatarData); - PACKET_TYPE_NAME_LOOKUP(InjectAudio); - PACKET_TYPE_NAME_LOOKUP(MixedAudio); - PACKET_TYPE_NAME_LOOKUP(MicrophoneAudioNoEcho); - PACKET_TYPE_NAME_LOOKUP(MicrophoneAudioWithEcho); - PACKET_TYPE_NAME_LOOKUP(BulkAvatarData); - PACKET_TYPE_NAME_LOOKUP(SilentAudioFrame); - PACKET_TYPE_NAME_LOOKUP(DomainListRequest); - PACKET_TYPE_NAME_LOOKUP(RequestAssignment); - PACKET_TYPE_NAME_LOOKUP(CreateAssignment); - PACKET_TYPE_NAME_LOOKUP(DomainConnectionDenied); - PACKET_TYPE_NAME_LOOKUP(MuteEnvironment); - PACKET_TYPE_NAME_LOOKUP(AudioStreamStats); - PACKET_TYPE_NAME_LOOKUP(OctreeStats); - PACKET_TYPE_NAME_LOOKUP(Jurisdiction); - PACKET_TYPE_NAME_LOOKUP(JurisdictionRequest); - PACKET_TYPE_NAME_LOOKUP(AvatarIdentity); - PACKET_TYPE_NAME_LOOKUP(AvatarBillboard); - PACKET_TYPE_NAME_LOOKUP(DomainConnectRequest); - PACKET_TYPE_NAME_LOOKUP(DomainServerRequireDTLS); - PACKET_TYPE_NAME_LOOKUP(NodeJsonStats); - PACKET_TYPE_NAME_LOOKUP(EntityQuery); - PACKET_TYPE_NAME_LOOKUP(EntityData); - PACKET_TYPE_NAME_LOOKUP(EntityErase); - PACKET_TYPE_NAME_LOOKUP(OctreeDataNack); - PACKET_TYPE_NAME_LOOKUP(StopNode); - PACKET_TYPE_NAME_LOOKUP(AudioEnvironment); - PACKET_TYPE_NAME_LOOKUP(EntityEditNack); - PACKET_TYPE_NAME_LOOKUP(ICEServerHeartbeat); - PACKET_TYPE_NAME_LOOKUP(DomainServerAddedNode); - PACKET_TYPE_NAME_LOOKUP(ICEServerQuery); - PACKET_TYPE_NAME_LOOKUP(ICEServerPeerInformation); - PACKET_TYPE_NAME_LOOKUP(ICEPing); - PACKET_TYPE_NAME_LOOKUP(ICEPingReply); - PACKET_TYPE_NAME_LOOKUP(EntityAdd); - PACKET_TYPE_NAME_LOOKUP(EntityEdit); - PACKET_TYPE_NAME_LOOKUP(DomainServerConnectionToken); + PACKET_TYPE_NAME_LOOKUP(PacketType::Unknown); + PACKET_TYPE_NAME_LOOKUP(PacketType::StunResponse); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainList); + PACKET_TYPE_NAME_LOOKUP(PacketType::Ping); + PACKET_TYPE_NAME_LOOKUP(PacketType::PingReply); + PACKET_TYPE_NAME_LOOKUP(PacketType::KillAvatar); + PACKET_TYPE_NAME_LOOKUP(PacketType::AvatarData); + PACKET_TYPE_NAME_LOOKUP(PacketType::InjectAudio); + PACKET_TYPE_NAME_LOOKUP(PacketType::MixedAudio); + PACKET_TYPE_NAME_LOOKUP(PacketType::MicrophoneAudioNoEcho); + PACKET_TYPE_NAME_LOOKUP(PacketType::MicrophoneAudioWithEcho); + PACKET_TYPE_NAME_LOOKUP(PacketType::BulkAvatarData); + PACKET_TYPE_NAME_LOOKUP(PacketType::SilentAudioFrame); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainListRequest); + PACKET_TYPE_NAME_LOOKUP(PacketType::RequestAssignment); + PACKET_TYPE_NAME_LOOKUP(PacketType::CreateAssignment); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainConnectionDenied); + PACKET_TYPE_NAME_LOOKUP(PacketType::MuteEnvironment); + PACKET_TYPE_NAME_LOOKUP(PacketType::AudioStreamStats); + PACKET_TYPE_NAME_LOOKUP(PacketType::OctreeStats); + PACKET_TYPE_NAME_LOOKUP(PacketType::Jurisdiction); + PACKET_TYPE_NAME_LOOKUP(PacketType::JurisdictionRequest); + PACKET_TYPE_NAME_LOOKUP(PacketType::AvatarIdentity); + PACKET_TYPE_NAME_LOOKUP(PacketType::AvatarBillboard); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainConnectRequest); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainServerRequireDTLS); + PACKET_TYPE_NAME_LOOKUP(PacketType::NodeJsonStats); + PACKET_TYPE_NAME_LOOKUP(PacketType::EntityQuery); + PACKET_TYPE_NAME_LOOKUP(PacketType::EntityData); + PACKET_TYPE_NAME_LOOKUP(PacketType::EntityErase); + PACKET_TYPE_NAME_LOOKUP(PacketType::OctreeDataNack); + PACKET_TYPE_NAME_LOOKUP(PacketType::StopNode); + PACKET_TYPE_NAME_LOOKUP(PacketType::AudioEnvironment); + PACKET_TYPE_NAME_LOOKUP(PacketType::EntityEditNack); + PACKET_TYPE_NAME_LOOKUP(PacketType::ICEServerHeartbeat); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainServerAddedNode); + PACKET_TYPE_NAME_LOOKUP(PacketType::ICEServerQuery); + PACKET_TYPE_NAME_LOOKUP(PacketType::ICEServerPeerInformation); + PACKET_TYPE_NAME_LOOKUP(PacketType::ICEPing); + PACKET_TYPE_NAME_LOOKUP(PacketType::ICEPingReply); + PACKET_TYPE_NAME_LOOKUP(PacketType::EntityAdd); + PACKET_TYPE_NAME_LOOKUP(PacketType::EntityEdit); + PACKET_TYPE_NAME_LOOKUP(PacketType::DomainServerConnectionToken); default: return QString("Type: ") + QString::number((int)packetType); } return QString("unexpected"); } -int numBytesForArithmeticCodedPacketType(PacketType::Value packetType) { - return (int) ceilf((float) packetType / 255); +uint qHash(const PacketType& key, uint seed) { + // seems odd that Qt couldn't figure out this cast itself, but this fixes a compile error after switch to + // strongly typed enum for PacketType + return qHash((quint8) key, seed); +} + +QDebug operator<<(QDebug debug, const PacketType& type) { + debug.nospace() << (uint8_t) type << " (" << qPrintable(nameForPacketType(type)) << ")"; + return debug.space(); } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index eb48bc8019..da702358a1 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -21,88 +21,80 @@ #include #include -#include "UUID.h" - -// NOTE: if adding a new packet packetType, you can replace one marked usable or add at the end -// NOTE: if you want the name of the packet packetType to be available for debugging or logging, update nameForPacketType() as well - -namespace PacketType { - enum Value { - Unknown, - StunResponse, - DomainList, - Ping, - PingReply, - KillAvatar, - AvatarData, - InjectAudio, - MixedAudio, - MicrophoneAudioNoEcho, - MicrophoneAudioWithEcho, - BulkAvatarData, - SilentAudioFrame, - DomainListRequest, - RequestAssignment, - CreateAssignment, - DomainConnectionDenied, - MuteEnvironment, - AudioStreamStats, - DomainServerPathQuery, - DomainServerPathResponse, - DomainServerAddedNode, - ICEServerPeerInformation, - ICEServerQuery, - OctreeStats, - Jurisdiction, - JurisdictionRequest, - AssignmentClientStatus, - NoisyMute, - AvatarIdentity, - AvatarBillboard, - DomainConnectRequest, - DomainServerRequireDTLS, - NodeJsonStats, - OctreeDataNack, - StopNode, - AudioEnvironment, - EntityEditNack, - ICEServerHeartbeat, - ICEPing, - ICEPingReply, - EntityData, - EntityQuery, - EntityAdd, - EntityErase, - EntityEdit, - DomainServerConnectionToken - }; +// If adding a new packet packetType, you can replace one marked usable or add at the end. +// If you want the name of the packet packetType to be available for debugging or logging, update nameForPacketType() as well +// This enum must hold 256 or fewer packet types (so the value is <= 255) since it is statically typed as a uint8_t +enum class PacketType : uint8_t { + Unknown, + StunResponse, + DomainList, + Ping, + PingReply, + KillAvatar, + AvatarData, + InjectAudio, + MixedAudio, + MicrophoneAudioNoEcho, + MicrophoneAudioWithEcho, + BulkAvatarData, + SilentAudioFrame, + DomainListRequest, + RequestAssignment, + CreateAssignment, + DomainConnectionDenied, + MuteEnvironment, + AudioStreamStats, + DomainServerPathQuery, + DomainServerPathResponse, + DomainServerAddedNode, + ICEServerPeerInformation, + ICEServerQuery, + OctreeStats, + Jurisdiction, + JurisdictionRequest, + AssignmentClientStatus, + NoisyMute, + AvatarIdentity, + AvatarBillboard, + DomainConnectRequest, + DomainServerRequireDTLS, + NodeJsonStats, + OctreeDataNack, + StopNode, + AudioEnvironment, + EntityEditNack, + ICEServerHeartbeat, + ICEPing, + ICEPingReply, + EntityData, + EntityQuery, + EntityAdd, + EntityErase, + EntityEdit, + DomainServerConnectionToken, + DomainSettingsRequest, + DomainSettings, + AssetGet, + AssetGetReply, + AssetUpload, + AssetUploadReply, + AssetGetInfo, + AssetGetInfoReply }; const int NUM_BYTES_MD5_HASH = 16; -const int MAX_PACKET_SIZE = 1450; -const int MAX_PACKET_HEADER_BYTES = 4 + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; - typedef char PacketVersion; -typedef uint16_t PacketSequenceNumber; -const PacketSequenceNumber DEFAULT_SEQUENCE_NUMBER = 0; +extern const QSet NON_VERIFIED_PACKETS; +extern const QSet NON_SOURCED_PACKETS; +extern const QSet RELIABLE_PACKETS; -typedef std::map PacketTypeSequenceMap; +QString nameForPacketType(PacketType packetType); +PacketVersion versionForPacketType(PacketType packetType); -extern const QSet NON_VERIFIED_PACKETS; -extern const QSet SEQUENCE_NUMBERED_PACKETS; -extern const QSet NON_SOURCED_PACKETS; - -QString nameForPacketType(PacketType::Value packetType); -PacketVersion versionForPacketType(PacketType::Value packetType); - -int numBytesForArithmeticCodedPacketType(PacketType::Value packetType); -int numBytesForPacketHeaderGivenPacketType(PacketType::Value packetType); -int packArithmeticallyCodedValue(int value, char* destination); - -int arithmeticCodingValueFromBuffer(const char* checkValue); -int numBytesArithmeticCodingFromBuffer(const char* checkValue); +uint qHash(const PacketType& key, uint seed); +QDebug operator<<(QDebug debug, const PacketType& type); const PacketVersion VERSION_OCTREE_HAS_FILE_BREAKS = 1; const PacketVersion VERSION_ENTITIES_HAVE_ANIMATION = 1; @@ -147,5 +139,6 @@ const PacketVersion VERSION_ENTITIES_PARTICLE_MODIFICATIONS = 39; const PacketVersion VERSION_ENTITIES_POLYVOX_NEIGHBORS = 40; const PacketVersion VERSION_ENTITIES_PARTICLE_RADIUS_PROPERTIES = 41; const PacketVersion VERSION_ENTITIES_PARTICLE_COLOR_PROPERTIES = 42; +const PacketVersion VERSION_ENTITIES_PROTOCOL_HEADER_SWAP = 43; #endif // hifi_PacketHeaders_h diff --git a/libraries/networking/src/udt/PacketList.cpp b/libraries/networking/src/udt/PacketList.cpp index 5dcacfa9b1..7b90276b62 100644 --- a/libraries/networking/src/udt/PacketList.cpp +++ b/libraries/networking/src/udt/PacketList.cpp @@ -13,15 +13,25 @@ #include -#include "Packet.h" +using namespace udt; -PacketList::PacketList(PacketType::Value packetType, QByteArray extendedHeader) : +PacketList::PacketList(PacketType packetType, QByteArray extendedHeader, bool isReliable, bool isOrdered) : _packetType(packetType), + _isReliable(isReliable), + _isOrdered(isOrdered), _extendedHeader(extendedHeader) { + Q_ASSERT_X(!(!_isReliable && _isOrdered), "PacketList", "Unreliable ordered PacketLists are not currently supported"); QIODevice::open(WriteOnly); } +PacketList::PacketList(PacketList&& other) : + _packets(std::move(other._packets)), + _packetType(other._packetType) +{ + +} + void PacketList::startSegment() { _segmentStartIndex = _currentPacket ? _currentPacket->pos() : _extendedHeader.size(); } @@ -30,15 +40,49 @@ void PacketList::endSegment() { _segmentStartIndex = -1; } +size_t PacketList::getDataSize() const { + size_t totalBytes = 0; + for (const auto& packet : _packets) { + totalBytes += packet->getDataSize(); + } + + if (_currentPacket) { + totalBytes += _currentPacket->getDataSize(); + } + + return totalBytes; +} + +size_t PacketList::getMessageSize() const { + size_t totalBytes = 0; + for (const auto& packet: _packets) { + totalBytes += packet->getPayloadSize(); + } + + if (_currentPacket) { + totalBytes += _currentPacket->getPayloadSize(); + } + + return totalBytes; +} + +std::unique_ptr PacketList::fromReceivedPackets(std::list>&& packets) { + auto packetList = std::unique_ptr(new PacketList(PacketType::Unknown, QByteArray(), true, true)); + packetList->_packets = std::move(packets); + return packetList; +} + std::unique_ptr PacketList::createPacket() { // use the static create method to create a new packet - return Packet::create(getType()); + // If this packet list is supposed to be ordered then we consider this to be part of a message + bool isPartOfMessage = _isOrdered; + return Packet::create(-1, _isReliable, isPartOfMessage); } std::unique_ptr PacketList::createPacketWithExtendedHeader() { // use the static create method to create a new packet auto packet = createPacket(); - + if (!_extendedHeader.isEmpty()) { // add the extended header to the front of the packet if (packet->write(_extendedHeader) == -1) { @@ -46,88 +90,113 @@ std::unique_ptr PacketList::createPacketWithExtendedHeader() { << "- make sure that _extendedHeader is not larger than the payload capacity."; } } - + return packet; } -qint64 PacketList::writeData(const char* data, qint64 maxSize) { - if (!_currentPacket) { - // we don't have a current packet, time to set one up - _currentPacket = createPacketWithExtendedHeader(); +QByteArray PacketList::getMessage() { + size_t sizeBytes = 0; + + for (const auto& packet : _packets) { + sizeBytes += packet->size(); } - - // check if this block of data can fit into the currentPacket - if (maxSize <= _currentPacket->bytesAvailableForWrite()) { - // it fits, just write it to the current packet - _currentPacket->write(data, maxSize); - - // return the number of bytes written - return maxSize; - } else { - // it does not fit - this may need to be in the next packet - - if (!_isOrdered) { - auto newPacket = createPacketWithExtendedHeader(); - - if (_segmentStartIndex >= 0) { - // We in the process of writing a segment for an unordered PacketList. - // We need to try and pull the first part of the segment out to our new packet - - // check now to see if this is an unsupported write - int numBytesToEnd = _currentPacket->bytesAvailableForWrite(); - - if ((newPacket->size() - numBytesToEnd) < maxSize) { - // this is an unsupported case - the segment is bigger than the size of an individual packet - // but the PacketList is not going to be sent ordered - qDebug() << "Error in PacketList::writeData - attempted to write a segment to an unordered packet that is" - << "larger than the payload size."; - Q_ASSERT(false); - } - - int segmentSize = _currentPacket->pos() - _segmentStartIndex; - - // copy from currentPacket where the segment started to the beginning of the newPacket - newPacket->write(_currentPacket->getPayload() + _segmentStartIndex, segmentSize); - - // the current segment now starts at the beginning of the new packet - _segmentStartIndex = _extendedHeader.size(); - - // shrink the current payload to the actual size of the packet - _currentPacket->setPayloadSize(_segmentStartIndex); - } - - // move the current packet to our list of packets - _packets.push_back(std::move(_currentPacket)); - - // write the data to the newPacket - newPacket->write(data, maxSize); - - // swap our current packet with the new packet - _currentPacket.swap(newPacket); - - // return the number of bytes written to the new packet - return maxSize; + + QByteArray data; + data.reserve(sizeBytes); + + for (auto& packet : _packets) { + data.append(packet->getPayload(), packet->getPayloadSize()); + } + + return data; +} + +qint64 PacketList::writeData(const char* data, qint64 maxSize) { + auto sizeRemaining = maxSize; + + while (sizeRemaining > 0) { + if (!_currentPacket) { + // we don't have a current packet, time to set one up + _currentPacket = createPacketWithExtendedHeader(); + } + + // check if this block of data can fit into the currentPacket + if (sizeRemaining <= _currentPacket->bytesAvailableForWrite()) { + // it fits, just write it to the current packet + _currentPacket->write(data, sizeRemaining); + + sizeRemaining = 0; } else { - // we're an ordered PacketList - let's fit what we can into the current packet and then put the leftover - // into a new packet - - int numBytesToEnd = _currentPacket->bytesAvailableForWrite(); - _currentPacket->write(data, numBytesToEnd); - - // move the current packet to our list of packets - _packets.push_back(std::move(_currentPacket)); - - // recursively call our writeData method for the remaining data to write to a new packet - return numBytesToEnd + writeData(data + numBytesToEnd, maxSize - numBytesToEnd); + // it does not fit - this may need to be in the next packet + + if (!_isOrdered) { + auto newPacket = createPacketWithExtendedHeader(); + + if (_segmentStartIndex >= 0) { + // We in the process of writing a segment for an unordered PacketList. + // We need to try and pull the first part of the segment out to our new packet + + // check now to see if this is an unsupported write + int numBytesToEnd = _currentPacket->bytesAvailableForWrite(); + + if ((newPacket->size() - numBytesToEnd) < sizeRemaining) { + // this is an unsupported case - the segment is bigger than the size of an individual packet + // but the PacketList is not going to be sent ordered + qDebug() << "Error in PacketList::writeData - attempted to write a segment to an unordered packet that is" + << "larger than the payload size."; + Q_ASSERT(false); + } + + int segmentSize = _currentPacket->pos() - _segmentStartIndex; + + // copy from currentPacket where the segment started to the beginning of the newPacket + newPacket->write(_currentPacket->getPayload() + _segmentStartIndex, segmentSize); + + // the current segment now starts at the beginning of the new packet + _segmentStartIndex = _extendedHeader.size(); + + // shrink the current payload to the actual size of the packet + _currentPacket->setPayloadSize(_segmentStartIndex); + } + + // move the current packet to our list of packets + _packets.push_back(std::move(_currentPacket)); + + // write the data to the newPacket + newPacket->write(data, sizeRemaining); + + // swap our current packet with the new packet + _currentPacket.swap(newPacket); + + // We've written all of the data, so set sizeRemaining to 0 + sizeRemaining = 0; + } else { + // we're an ordered PacketList - let's fit what we can into the current packet and then put the leftover + // into a new packet + + int numBytesToEnd = _currentPacket->bytesAvailableForWrite(); + _currentPacket->write(data, numBytesToEnd); + + // Remove number of bytes written from sizeRemaining + sizeRemaining -= numBytesToEnd; + + // Move the data pointer forward + data += numBytesToEnd; + + // move the current packet to our list of packets + _packets.push_back(std::move(_currentPacket)); + } } } + + return maxSize; } void PacketList::closeCurrentPacket(bool shouldSendEmpty) { if (shouldSendEmpty && !_currentPacket) { _currentPacket = createPacketWithExtendedHeader(); } - + if (_currentPacket) { // move the current packet to our list of packets _packets.push_back(std::move(_currentPacket)); diff --git a/libraries/networking/src/udt/PacketList.h b/libraries/networking/src/udt/PacketList.h index 30288dcaab..05800a1b26 100644 --- a/libraries/networking/src/udt/PacketList.h +++ b/libraries/networking/src/udt/PacketList.h @@ -16,33 +16,57 @@ #include +#include "Packet.h" #include "PacketHeaders.h" +class LimitedNodeList; + +namespace udt { + class Packet; class PacketList : public QIODevice { Q_OBJECT public: - PacketList(PacketType::Value packetType, QByteArray extendedHeader = QByteArray()); + PacketList(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false); + PacketList(PacketList&& other); + + static std::unique_ptr fromReceivedPackets(std::list>&& packets); virtual bool isSequential() const { return true; } + + bool isReliable() const { return _isReliable; } + bool isOrdered() const { return _isOrdered; } void startSegment(); void endSegment(); - PacketType::Value getType() const { return _packetType; } + PacketType getType() const { return _packetType; } int getNumPackets() const { return _packets.size() + (_currentPacket ? 1 : 0); } + + QByteArray getExtendedHeader() const { return _extendedHeader; } + + size_t getDataSize() const; + size_t getMessageSize() const; void closeCurrentPacket(bool shouldSendEmpty = false); - + + QByteArray getMessage(); + template qint64 readPrimitive(T* data); template qint64 writePrimitive(const T& data); + std::list> _packets; protected: virtual qint64 writeData(const char* data, qint64 maxSize); virtual qint64 readData(char* data, qint64 maxSize) { return 0; } + PacketType _packetType; + private: - friend class LimitedNodeList; + friend class ::LimitedNodeList; + friend class Socket; + friend class SendQueue; + friend class NLPacketList; PacketList(const PacketList& other) = delete; PacketList& operator=(const PacketList& other) = delete; @@ -54,11 +78,11 @@ private: virtual std::unique_ptr createPacket(); std::unique_ptr createPacketWithExtendedHeader(); - PacketType::Value _packetType; + Packet::MessageNumber _messageNumber; + bool _isReliable = false; bool _isOrdered = false; std::unique_ptr _currentPacket; - std::list> _packets; int _segmentStartIndex = -1; @@ -82,5 +106,7 @@ template std::unique_ptr PacketList::takeFront() { _packets.pop_front(); return std::unique_ptr(dynamic_cast(packet.release())); } + +} #endif // hifi_PacketList_h diff --git a/libraries/networking/src/udt/PacketTimeWindow.cpp b/libraries/networking/src/udt/PacketTimeWindow.cpp new file mode 100644 index 0000000000..00eb43c7e6 --- /dev/null +++ b/libraries/networking/src/udt/PacketTimeWindow.cpp @@ -0,0 +1,126 @@ +// +// PacketTimeWindow.cpp +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-28. +// 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 "PacketTimeWindow.h" + +#include +#include + +#include + +using namespace udt; +using namespace std::chrono; + +static const int DEFAULT_PACKET_INTERVAL_MICROSECONDS = 1000000; // 1s +static const int DEFAULT_PROBE_INTERVAL_MICROSECONDS = 1000; // 1ms + +PacketTimeWindow::PacketTimeWindow(int numPacketIntervals, int numProbeIntervals) : + _numPacketIntervals(numPacketIntervals), + _numProbeIntervals(numProbeIntervals), + _packetIntervals(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS), + _probeIntervals(_numProbeIntervals, DEFAULT_PROBE_INTERVAL_MICROSECONDS) +{ + +} + +void PacketTimeWindow::reset() { + _packetIntervals.assign(_numPacketIntervals, DEFAULT_PACKET_INTERVAL_MICROSECONDS); + _probeIntervals.assign(_numProbeIntervals, DEFAULT_PROBE_INTERVAL_MICROSECONDS); +} + +template +int median(Iterator begin, Iterator end) { + // use std::nth_element to grab the middle - for an even number of elements this is the upper middle + Iterator middle = begin + (end - begin) / 2; + std::nth_element(begin, middle, end); + + if ((end - begin) % 2 != 0) { + // odd number of elements, just return the middle + return *middle; + } else { + // even number of elements, return the mean of the upper middle and the lower middle + Iterator lowerMiddle = std::max_element(begin, middle); + return (*middle + *lowerMiddle) / 2; + } +} + +int32_t meanOfMedianFilteredValues(std::vector intervals, int numValues, int valuesRequired = 0) { + // grab the median value of the intervals vector + int intervalsMedian = median(intervals.begin(), intervals.end()); + + // figure out our bounds for median filtering + static const int MEDIAN_FILTERING_BOUND_MULTIPLIER = 8; + int upperBound = intervalsMedian * MEDIAN_FILTERING_BOUND_MULTIPLIER; + int lowerBound = intervalsMedian / MEDIAN_FILTERING_BOUND_MULTIPLIER; + + int sum = 0; + int count = 0; + + // sum the values that are inside the median filtered bounds + for (auto& interval : intervals) { + if ((interval < upperBound) && (interval > lowerBound)) { + ++count; + sum += interval; + } + } + + // make sure we hit our threshold of values required + if (count >= valuesRequired) { + // return the frequency (per second) for the mean interval + static const double USECS_PER_SEC = 1000000.0; + return (int32_t) ceil(USECS_PER_SEC / (((double) sum) / ((double) count))); + } else { + return 0; + } +} + +int32_t PacketTimeWindow::getPacketReceiveSpeed() const { + // return the mean value of median filtered values (per second) - or zero if there are too few filtered values + return meanOfMedianFilteredValues(_packetIntervals, _numPacketIntervals, _numPacketIntervals / 2); +} + +int32_t PacketTimeWindow::getEstimatedBandwidth() const { + // return mean value of median filtered values (per second) + return meanOfMedianFilteredValues(_probeIntervals, _numProbeIntervals); +} + +void PacketTimeWindow::onPacketArrival() { + // take the current time + auto now = high_resolution_clock::now(); + + // record the interval between this packet and the last one + _packetIntervals[_currentPacketInterval++] = duration_cast(now - _lastPacketTime).count(); + + // reset the currentPacketInterval index when it wraps + if (_currentPacketInterval == _numPacketIntervals) { + _currentPacketInterval = 0; + } + + // remember this as the last packet arrival time + _lastPacketTime = now; +} + +void PacketTimeWindow::onProbePair1Arrival() { + // take the current time as the first probe time + _firstProbeTime = high_resolution_clock::now(); +} + +void PacketTimeWindow::onProbePair2Arrival() { + // store the interval between the two probes + auto now = high_resolution_clock::now(); + + _probeIntervals[_currentProbeInterval++] = duration_cast(now - _firstProbeTime).count(); + + // reset the currentProbeInterval index when it wraps + if (_currentProbeInterval == _numProbeIntervals) { + _currentProbeInterval = 0; + } +} diff --git a/libraries/networking/src/udt/PacketTimeWindow.h b/libraries/networking/src/udt/PacketTimeWindow.h new file mode 100644 index 0000000000..a8a4e0c8b5 --- /dev/null +++ b/libraries/networking/src/udt/PacketTimeWindow.h @@ -0,0 +1,50 @@ +// +// PacketTimeWindow.h +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-28. +// 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 +// + +#pragma once + +#ifndef hifi_PacketTimeWindow_h +#define hifi_PacketTimeWindow_h + +#include +#include + +namespace udt { + +class PacketTimeWindow { +public: + PacketTimeWindow(int numPacketIntervals = 16, int numProbeIntervals = 16); + + void onPacketArrival(); + void onProbePair1Arrival(); + void onProbePair2Arrival(); + + int32_t getPacketReceiveSpeed() const; + int32_t getEstimatedBandwidth() const; + + void reset(); +private: + int _numPacketIntervals { 0 }; // the number of packet intervals to store + int _numProbeIntervals { 0 }; // the number of probe intervals to store + + int _currentPacketInterval { 0 }; // index for the current packet interval + int _currentProbeInterval { 0 }; // index for the current probe interval + + std::vector _packetIntervals; // vector of microsecond intervals between packet arrivals + std::vector _probeIntervals; // vector of microsecond intervals between probe pair arrivals + + std::chrono::high_resolution_clock::time_point _lastPacketTime; // the time_point when last packet arrived + std::chrono::high_resolution_clock::time_point _firstProbeTime; // the time_point when first probe in pair arrived +}; + +} + +#endif // hifi_PacketTimeWindow_h diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp new file mode 100644 index 0000000000..28f71ec93f --- /dev/null +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -0,0 +1,535 @@ +// +// SendQueue.cpp +// libraries/networking/src/udt +// +// Created by Clement on 7/21/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 "SendQueue.h" + +#include +#include + +#include +#include +#include + +#include + +#include "../NetworkLogging.h" +#include "ControlPacket.h" +#include "Packet.h" +#include "PacketList.h" +#include "Socket.h" + +using namespace udt; + +class DoubleLock { +public: + DoubleLock(std::mutex& mutex1, std::mutex& mutex2) : _mutex1(mutex1), _mutex2(mutex2) { } + + DoubleLock(const DoubleLock&) = delete; + DoubleLock& operator=(const DoubleLock&) = delete; + + // Either locks all the mutexes or none of them + bool try_lock() { return (std::try_lock(_mutex1, _mutex2) == -1); } + + // Locks all the mutexes + void lock() { std::lock(_mutex1, _mutex2); } + + // Undefined behavior if not locked + void unlock() { _mutex1.unlock(); _mutex2.unlock(); } + +private: + std::mutex& _mutex1; + std::mutex& _mutex2; +}; + +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { + auto queue = std::unique_ptr(new SendQueue(socket, destination)); + + Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); + + // Setup queue private thread + QThread* thread = new QThread; + thread->setObjectName("Networking: SendQueue " + destination.objectName()); // Name thread for easier debug + + connect(thread, &QThread::started, queue.get(), &SendQueue::run); + + connect(queue.get(), &QObject::destroyed, thread, &QThread::quit); // Thread auto cleanup + connect(thread, &QThread::finished, thread, &QThread::deleteLater); // Thread auto cleanup + + // Move queue to private thread and start it + queue->moveToThread(thread); + thread->start(); + + return std::move(queue); +} + +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : + _socket(socket), + _destination(dest) +{ + +} + +void SendQueue::queuePacket(std::unique_ptr packet) { + { + std::unique_lock locker(_packetsLock); + + _packets.push_back(std::move(packet)); + + // unlock the mutex before we notify + locker.unlock(); + + // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets + _emptyCondition.notify_one(); + } + if (!this->thread()->isRunning()) { + this->thread()->start(); + } +} + +void SendQueue::queuePacketList(std::unique_ptr packetList) { + Q_ASSERT(packetList->_packets.size() > 0); + + { + auto messageNumber = getNextMessageNumber(); + + if (packetList->_packets.size() == 1) { + auto& packet = packetList->_packets.front(); + + packet->setPacketPosition(Packet::PacketPosition::ONLY); + packet->writeMessageNumber(messageNumber); + } else { + bool haveMarkedFirstPacket = false; + auto end = packetList->_packets.end(); + auto lastElement = --packetList->_packets.end(); + for (auto it = packetList->_packets.begin(); it != end; ++it) { + auto& packet = *it; + + if (!haveMarkedFirstPacket) { + packet->setPacketPosition(Packet::PacketPosition::FIRST); + haveMarkedFirstPacket = true; + } else if (it == lastElement) { + packet->setPacketPosition(Packet::PacketPosition::LAST); + } else { + packet->setPacketPosition(Packet::PacketPosition::MIDDLE); + } + + packet->writeMessageNumber(messageNumber); + } + } + + std::unique_lock locker(_packetsLock); + + _packets.splice(_packets.end(), packetList->_packets); + + // unlock the mutex so we can notify + locker.unlock(); + + // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets + _emptyCondition.notify_one(); + } + + if (!this->thread()->isRunning()) { + this->thread()->start(); + } +} + +void SendQueue::stop() { + _isRunning = false; + + // in case we're waiting to send another handshake, release the condition_variable now so we cleanup sooner + _handshakeACKCondition.notify_one(); + + // in case the empty condition is waiting for packets/loss release it now so that the queue is cleaned up + _emptyCondition.notify_one(); +} + +void SendQueue::sendPacket(const Packet& packet) { + _socket->writeDatagram(packet.getData(), packet.getDataSize(), _destination); +} + +void SendQueue::ack(SequenceNumber ack) { + // this is a response from the client, re-set our timeout expiry and our last response time + _timeoutExpiryCount = 0; + _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); + + if (_lastACKSequenceNumber == (uint32_t) ack) { + return; + } + + { + // remove any ACKed packets from the map of sent packets + QWriteLocker locker(&_sentLock); + for (auto seq = SequenceNumber { (uint32_t) _lastACKSequenceNumber }; seq <= ack; ++seq) { + _sentPackets.erase(seq); + } + } + + { // remove any sequence numbers equal to or lower than this ACK in the loss list + std::lock_guard nakLocker(_naksLock); + + if (_naks.getLength() > 0 && _naks.getFirstSequenceNumber() <= ack) { + _naks.remove(_naks.getFirstSequenceNumber(), ack); + } + } + + _lastACKSequenceNumber = (uint32_t) ack; +} + +void SendQueue::nak(SequenceNumber start, SequenceNumber end) { + // this is a response from the client, re-set our timeout expiry + _timeoutExpiryCount = 0; + _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); + + std::unique_lock nakLocker(_naksLock); + + _naks.insert(start, end); + + // unlock the locked mutex before we notify + nakLocker.unlock(); + + // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send + _emptyCondition.notify_one(); +} + +void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { + // this is a response from the client, re-set our timeout expiry + _timeoutExpiryCount = 0; + _lastReceiverResponse = uint64_t(QDateTime::currentMSecsSinceEpoch()); + + std::unique_lock nakLocker(_naksLock); + _naks.clear(); + + SequenceNumber first, second; + while (packet.bytesLeftToRead() >= (qint64)(2 * sizeof(SequenceNumber))) { + packet.readPrimitive(&first); + packet.readPrimitive(&second); + + if (first == second) { + _naks.append(first); + } else { + _naks.append(first, second); + } + } + + // unlock the mutex before we notify + nakLocker.unlock(); + + // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send + _emptyCondition.notify_one(); +} + +void SendQueue::handshakeACK() { + std::unique_lock locker { _handshakeMutex }; + + _hasReceivedHandshakeACK = true; + + // unlock the mutex and notify on the handshake ACK condition + locker.unlock(); + + _handshakeACKCondition.notify_one(); +} + +SequenceNumber SendQueue::getNextSequenceNumber() { + _atomicCurrentSequenceNumber = (SequenceNumber::Type)++_currentSequenceNumber; + return _currentSequenceNumber; +} + +uint32_t SendQueue::getNextMessageNumber() { + static const MessageNumber MAX_MESSAGE_NUMBER = MessageNumber(1) << MESSAGE_NUMBER_BITS; + _currentMessageNumber = (_currentMessageNumber + 1) % MAX_MESSAGE_NUMBER; + return _currentMessageNumber; +} + +void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, SequenceNumber sequenceNumber) { + // write the sequence number and send the packet + newPacket->writeSequenceNumber(sequenceNumber); + sendPacket(*newPacket); + + // Save packet/payload size before we move it + auto packetSize = newPacket->getDataSize(); + auto payloadSize = newPacket->getPayloadSize(); + + { + // Insert the packet we have just sent in the sent list + QWriteLocker locker(&_sentLock); + _sentPackets[newPacket->getSequenceNumber()].swap(newPacket); + } + Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list"); + + emit packetSent(packetSize, payloadSize); +} + +void SendQueue::run() { + _isRunning = true; + + while (_isRunning) { + // Record how long the loop takes to execute + auto loopStartTimestamp = high_resolution_clock::now(); + + std::unique_lock handshakeLock { _handshakeMutex }; + + if (!_hasReceivedHandshakeACK) { + // we haven't received a handshake ACK from the client + // if it has been at least 100ms since we last sent a handshake, send another now + + // hold the time of last send in a static + static auto lastSendHandshake = high_resolution_clock::time_point(); + + static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100); + + // calculation the duration since the last handshake send + auto sinceLastHandshake = std::chrono::duration_cast(high_resolution_clock::now() + - lastSendHandshake); + + if (sinceLastHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) { + + // it has been long enough since last handshake, send another + static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0); + _socket->writeBasePacket(*handshakePacket, _destination); + + lastSendHandshake = high_resolution_clock::now(); + } + + // we wait for the ACK or the re-send interval to expire + _handshakeACKCondition.wait_until(handshakeLock, + high_resolution_clock::now() + + HANDSHAKE_RESEND_INTERVAL_MS); + + // Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake. + // Either way let's continue processing - no packets will be sent if no handshake ACK has been received. + } + + handshakeLock.unlock(); + + bool sentAPacket = maybeResendPacket(); + + // if we didn't find a packet to re-send AND we think we can fit a new packet on the wire + // (this is according to the current flow window size) then we send out a new packet + if (_hasReceivedHandshakeACK && !sentAPacket) { + if (seqlen(SequenceNumber { (uint32_t) _lastACKSequenceNumber }, _currentSequenceNumber) <= _flowWindowSize) { + sentAPacket = maybeSendNewPacket(); + } + } + + // since we're a while loop, give the thread a chance to process events + QCoreApplication::processEvents(); + + // we just processed events so check now if we were just told to stop + if (!_isRunning) { + break; + } + + if (_hasReceivedHandshakeACK && !sentAPacket) { + // check if it is time to break this connection + + // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been + // at least 5 seconds + + static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16; + static const int MIN_SECONDS_BEFORE_INACTIVE_MS = 5 * 1000; + + auto sinceEpochNow = QDateTime::currentMSecsSinceEpoch(); + + if (_timeoutExpiryCount >= NUM_TIMEOUTS_BEFORE_INACTIVE + && (sinceEpochNow - _lastReceiverResponse) > MIN_SECONDS_BEFORE_INACTIVE_MS) { + // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, + // then signal the queue is inactive and return so it can be cleaned up + + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts" + << "and 10s before receiving any ACK/NAK and is now inactive. Stopping."; + #endif + + deactivate(); + + return; + } else { + // During our processing above we didn't send any packets + + // If that is still the case we should use a condition_variable_any to sleep until we have data to handle. + // To confirm that the queue of packets and the NAKs list are still both empty we'll need to use the DoubleLock + DoubleLock doubleLock(_packetsLock, _naksLock); + + if (doubleLock.try_lock()) { + // The packets queue and loss list mutexes are now both locked - check if they're still both empty + + if (_packets.empty() && _naks.getLength() == 0) { + if (uint32_t(_lastACKSequenceNumber) == uint32_t(_currentSequenceNumber)) { + // we've sent the client as much data as we have (and they've ACKed it) + // either wait for new data to send or 5 seconds before cleaning up the queue + static const auto EMPTY_QUEUES_INACTIVE_TIMEOUT = std::chrono::seconds(5); + + // use our condition_variable_any to wait + auto cvStatus = _emptyCondition.wait_for(doubleLock, EMPTY_QUEUES_INACTIVE_TIMEOUT); + + // we have the double lock again - Make sure to unlock it + doubleLock.unlock(); + + if (cvStatus == std::cv_status::timeout) { + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "SendQueue to" << _destination << "has been empty for" + << EMPTY_QUEUES_INACTIVE_TIMEOUT.count() + << "seconds and receiver has ACKed all packets." + << "The queue is now inactive and will be stopped."; + #endif + + deactivate(); + + return; + } + } else { + // We think the client is still waiting for data (based on the sequence number gap) + // Let's wait either for a response from the client or until the estimated timeout + auto waitDuration = std::chrono::microseconds(_estimatedTimeout); + + // use our condition_variable_any to wait + auto cvStatus = _emptyCondition.wait_for(doubleLock, waitDuration); + + if (cvStatus == std::cv_status::timeout) { + // increase the number of timeouts + ++_timeoutExpiryCount; + + if (SequenceNumber(_lastACKSequenceNumber) < _currentSequenceNumber) { + // after a timeout if we still have sent packets that the client hasn't ACKed we + // add them to the loss list + + // Note that thanks to the DoubleLock we have the _naksLock right now + _naks.append(SequenceNumber(_lastACKSequenceNumber) + 1, _currentSequenceNumber); + } + } + + // we have the double lock again - Make sure to unlock it + doubleLock.unlock(); + + // skip to the next iteration + continue; + } + } else { + // we got the try_lock but failed the other conditionals so we need to unlock + doubleLock.unlock(); + } + } + } + } + + auto loopEndTimestamp = high_resolution_clock::now(); + + // sleep as long as we need until next packet send, if we can + auto timeToSleep = (loopStartTimestamp + std::chrono::microseconds(_packetSendPeriod)) - loopEndTimestamp; + if (timeToSleep > timeToSleep.zero()) { + std::this_thread::sleep_for(timeToSleep); + } + } +} + +bool SendQueue::maybeSendNewPacket() { + // we didn't re-send a packet, so time to send a new one + std::unique_lock locker(_packetsLock); + + if (_packets.size() > 0) { + SequenceNumber nextNumber = getNextSequenceNumber(); + + // grab the first packet we will send + std::unique_ptr firstPacket; + firstPacket.swap(_packets.front()); + _packets.pop_front(); + + std::unique_ptr secondPacket; + bool shouldSendPairTail = false; + + if (((uint32_t) nextNumber & 0xF) == 0) { + // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets + // pull off a second packet if we can before we unlock + shouldSendPairTail = true; + + if (_packets.size() > 0) { + secondPacket.swap(_packets.front()); + _packets.pop_front(); + } + } + + // unlock the packets, we're done pulling + locker.unlock(); + + // definitely send the first packet + sendNewPacketAndAddToSentList(move(firstPacket), nextNumber); + + // do we have a second in a pair to send as well? + if (secondPacket) { + sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber()); + } else if (shouldSendPairTail) { + // we didn't get a second packet to send in the probe pair + // send a control packet of type ProbePairTail so the receiver can still do + // proper bandwidth estimation + static auto pairTailPacket = ControlPacket::create(ControlPacket::ProbeTail); + _socket->writeBasePacket(*pairTailPacket, _destination); + } + + // We sent our packet(s), return here + return true; + } + + // No packets were sent + return false; +} + +bool SendQueue::maybeResendPacket() { + + // the following while makes sure that we find a packet to re-send, if there is one + while (true) { + + std::unique_lock naksLocker(_naksLock); + + if (_naks.getLength() > 0) { + // pull the sequence number we need to re-send + SequenceNumber resendNumber = _naks.popFirstSequenceNumber(); + naksLocker.unlock(); + + // pull the packet to re-send from the sent packets list + QReadLocker sentLocker(&_sentLock); + + // see if we can find the packet to re-send + auto it = _sentPackets.find(resendNumber); + + if (it != _sentPackets.end()) { + // we found the packet - grab it + auto& resendPacket = *(it->second); + + // unlock the sent packets + sentLocker.unlock(); + + // send it off + sendPacket(resendPacket); + emit packetRetransmitted(); + + // Signal that we did resend a packet + return true; + } else { + // we didn't find this packet in the sentPackets queue - assume this means it was ACKed + // we'll fire the loop again to see if there is another to re-send + continue; + } + } + + // break from the while, we didn't resend a packet + break; + } + + // No packet was resent + return false; +} + +void SendQueue::deactivate() { + // this queue is inactive - emit that signal and stop the while + emit queueInactive(); + + _isRunning = false; +} diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h new file mode 100644 index 0000000000..564e74a3fb --- /dev/null +++ b/libraries/networking/src/udt/SendQueue.h @@ -0,0 +1,134 @@ +// +// SendQueue.h +// libraries/networking/src/udt +// +// Created by Clement on 7/21/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 +// + +#ifndef hifi_SendQueue_h +#define hifi_SendQueue_h + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../HifiSockAddr.h" + +#include "Constants.h" +#include "SequenceNumber.h" +#include "LossList.h" + +namespace udt { + +class BasePacket; +class ControlPacket; +class Packet; +class PacketList; +class Socket; + +using MessageNumber = uint32_t; + +class SendQueue : public QObject { + Q_OBJECT + +public: + using high_resolution_clock = std::chrono::high_resolution_clock; + using time_point = high_resolution_clock::time_point; + + static std::unique_ptr create(Socket* socket, HifiSockAddr destination); + + void queuePacket(std::unique_ptr packet); + void queuePacketList(std::unique_ptr packetList); + + SequenceNumber getCurrentSequenceNumber() const { return SequenceNumber(_atomicCurrentSequenceNumber); } + + void setFlowWindowSize(int flowWindowSize) { _flowWindowSize = flowWindowSize; } + + int getPacketSendPeriod() const { return _packetSendPeriod; } + void setPacketSendPeriod(int newPeriod) { _packetSendPeriod = newPeriod; } + + void setEstimatedTimeout(int estimatedTimeout) { _estimatedTimeout = estimatedTimeout; } + +public slots: + void stop(); + + void ack(SequenceNumber ack); + void nak(SequenceNumber start, SequenceNumber end); + void overrideNAKListFromPacket(ControlPacket& packet); + void handshakeACK(); + +signals: + void packetSent(int dataSize, int payloadSize); + void packetRetransmitted(); + + void queueInactive(); + +private slots: + void run(); + +private: + SendQueue(Socket* socket, HifiSockAddr dest); + SendQueue(SendQueue& other) = delete; + SendQueue(SendQueue&& other) = delete; + + void sendPacket(const Packet& packet); + void sendNewPacketAndAddToSentList(std::unique_ptr newPacket, SequenceNumber sequenceNumber); + + bool maybeSendNewPacket(); // Figures out what packet to send next + bool maybeResendPacket(); // Determines whether to resend a packet and which one + + void deactivate(); // makes the queue inactive and cleans it up + + // Increments current sequence number and return it + SequenceNumber getNextSequenceNumber(); + MessageNumber getNextMessageNumber(); + + mutable std::mutex _packetsLock; // Protects the packets to be sent list. + std::list> _packets; // List of packets to be sent + + Socket* _socket { nullptr }; // Socket to send packet on + HifiSockAddr _destination; // Destination addr + + std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number + + MessageNumber _currentMessageNumber { 0 }; + SequenceNumber _currentSequenceNumber; // Last sequence number sent out + std::atomic _atomicCurrentSequenceNumber { 0 };// Atomic for last sequence number sent out + + std::atomic _packetSendPeriod { 0 }; // Interval between two packet send event in microseconds, set from CC + std::atomic _isRunning { false }; + + std::atomic _estimatedTimeout { 0 }; // Estimated timeout, set from CC + std::atomic _timeoutExpiryCount { 0 }; // The number of times the timeout has expired without response from client + std::atomic _lastReceiverResponse { 0 }; // Timestamp for the last time we got new data from the receiver (ACK/NAK) + + std::atomic _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC + + mutable std::mutex _naksLock; // Protects the naks list. + LossList _naks; // Sequence numbers of packets to resend + + mutable QReadWriteLock _sentLock; // Protects the sent packet list + std::unordered_map> _sentPackets; // Packets waiting for ACK. + + std::mutex _handshakeMutex; // Protects the handshake ACK condition_variable + std::atomic _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client + std::condition_variable _handshakeACKCondition; + + std::condition_variable_any _emptyCondition; +}; + +} + +#endif // hifi_SendQueue_h diff --git a/libraries/networking/src/udt/SequenceNumber.cpp b/libraries/networking/src/udt/SequenceNumber.cpp new file mode 100644 index 0000000000..3e94c35d44 --- /dev/null +++ b/libraries/networking/src/udt/SequenceNumber.cpp @@ -0,0 +1,29 @@ +// +// SequenceNumber.cpp +// libraries/networking/src/udt +// +// Created by Clement on 7/23/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 "SequenceNumber.h" + +int udt::seqlen(const SequenceNumber& seq1, const SequenceNumber& seq2) { + return (seq1._value <= seq2._value) ? (seq2._value - seq1._value + 1) + : (seq2._value - seq1._value + SequenceNumber::MAX + 2); +} + +int udt::seqoff(const SequenceNumber& seq1, const SequenceNumber& seq2) { + if (glm::abs(seq1._value - seq2._value) < SequenceNumber::THRESHOLD) { + return seq2._value - seq1._value; + } + + if (seq1._value < seq2._value) { + return seq2._value - seq1._value - SequenceNumber::MAX - 1; + } + + return seq2._value - seq1._value + SequenceNumber::MAX + 1; +} diff --git a/libraries/networking/src/udt/SequenceNumber.h b/libraries/networking/src/udt/SequenceNumber.h new file mode 100644 index 0000000000..a75f3478b8 --- /dev/null +++ b/libraries/networking/src/udt/SequenceNumber.h @@ -0,0 +1,151 @@ +// +// SequenceNumber.h +// libraries/networking/src/udt +// +// Created by Clement on 7/23/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 +// + +#ifndef hifi_SequenceNumber_h +#define hifi_SequenceNumber_h + +#include + +#include + +namespace udt { + +class SequenceNumber { +public: + // Base type of sequence numbers + using Type = int32_t; + using UType = uint32_t; + + // Values are for 29 bit SequenceNumber + static const Type THRESHOLD = 0x0FFFFFFF; // threshold for comparing sequence numbers + static const Type MAX = 0x1FFFFFFF; // maximum sequence number used in UDT + + SequenceNumber() = default; + SequenceNumber(const SequenceNumber& other) : _value(other._value) {} + + // Only explicit conversions + explicit SequenceNumber(char* value) { _value = (*reinterpret_cast(value)) & MAX; } + explicit SequenceNumber(Type value) { _value = (value <= MAX) ? ((value >= 0) ? value : 0) : MAX; } + explicit SequenceNumber(UType value) { _value = (value <= MAX) ? value : MAX; } + explicit operator Type() { return _value; } + explicit operator UType() { return static_cast(_value); } + + inline SequenceNumber& operator++() { + _value = (_value + 1) % (MAX + 1); + return *this; + } + inline SequenceNumber& operator--() { + _value = (_value == 0) ? MAX : _value - 1; + return *this; + } + inline SequenceNumber operator++(int) { + SequenceNumber before = *this; + ++(*this); + return before; + } + inline SequenceNumber operator--(int) { + SequenceNumber before = *this; + --(*this); + return before; + } + + inline SequenceNumber& operator=(const SequenceNumber& other) { + _value = other._value; + return *this; + } + inline SequenceNumber& operator+=(Type inc) { + _value = (_value + inc > MAX) ? _value + inc - (MAX + 1) : _value + inc; + return *this; + } + inline SequenceNumber& operator-=(Type dec) { + _value = (_value < dec) ? MAX - (dec - _value + 1) : _value - dec; + return *this; + } + + inline bool operator==(const SequenceNumber& other) const { + return _value == other._value; + } + inline bool operator!=(const SequenceNumber& other) const { + return _value != other._value; + } + + friend bool operator<(const SequenceNumber& a, const SequenceNumber& b); + friend bool operator>(const SequenceNumber& a, const SequenceNumber& b); + friend bool operator<=(const SequenceNumber& a, const SequenceNumber& b); + friend bool operator>=(const SequenceNumber& a, const SequenceNumber& b); + + friend SequenceNumber operator+(const SequenceNumber a, const Type& b); + friend SequenceNumber operator+(const Type& a, const SequenceNumber b); + friend SequenceNumber operator-(const SequenceNumber a, const Type& b); + friend SequenceNumber operator-(const Type& a, const SequenceNumber b); + + friend int seqlen(const SequenceNumber& seq1, const SequenceNumber& seq2); + friend int seqoff(const SequenceNumber& seq1, const SequenceNumber& seq2); + +private: + Type _value { 0 }; + + friend struct std::hash; +}; +static_assert(sizeof(SequenceNumber) == sizeof(uint32_t), "SequenceNumber invalid size"); + + +inline bool operator<(const SequenceNumber& a, const SequenceNumber& b) { + return (glm::abs(a._value - b._value) < SequenceNumber::THRESHOLD) ? a._value < b._value : b._value < a._value; +} + +inline bool operator>(const SequenceNumber& a, const SequenceNumber& b) { + return (glm::abs(a._value - b._value) < SequenceNumber::THRESHOLD) ? a._value > b._value : b._value > a._value; +} + +inline bool operator<=(const SequenceNumber& a, const SequenceNumber& b) { + return (glm::abs(a._value - b._value) < SequenceNumber::THRESHOLD) ? a._value <= b._value : b._value <= a._value; +} + +inline bool operator>=(const SequenceNumber& a, const SequenceNumber& b) { + return (glm::abs(a._value - b._value) < SequenceNumber::THRESHOLD) ? a._value >= b._value : b._value >= a._value; +} + + +inline SequenceNumber operator+(SequenceNumber a, const SequenceNumber::Type& b) { + a += b; + return a; +} + +inline SequenceNumber operator+(const SequenceNumber::Type& a, SequenceNumber b) { + b += a; + return b; +} + +inline SequenceNumber operator-(SequenceNumber a, const SequenceNumber::Type& b) { + a -= b; + return a; +} + +inline SequenceNumber operator-(const SequenceNumber::Type& a, SequenceNumber b) { + b -= a; + return b; +} + +int seqlen(const SequenceNumber& seq1, const SequenceNumber& seq2); +int seqoff(const SequenceNumber& seq1, const SequenceNumber& seq2); + +} + +namespace std { + template<> struct hash { + size_t operator()(const udt::SequenceNumber& SequenceNumber) const { + return hash()(SequenceNumber._value); + } + }; +} + +#endif // hifi_SequenceNumber_h diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp new file mode 100644 index 0000000000..37bc88ac49 --- /dev/null +++ b/libraries/networking/src/udt/Socket.cpp @@ -0,0 +1,336 @@ +// +// Socket.cpp +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-20. +// 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 "Socket.h" + +#include + +#include + +#include "../NetworkLogging.h" +#include "Connection.h" +#include "ControlPacket.h" +#include "Packet.h" +#include "../NLPacket.h" +#include "PacketList.h" + +using namespace udt; + +Q_DECLARE_METATYPE(Packet*); +Q_DECLARE_METATYPE(PacketList*); + +Socket::Socket(QObject* parent) : + QObject(parent), + _synTimer(new QTimer(this)) +{ + qRegisterMetaType(); + qRegisterMetaType(); + + connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams); + + // make sure our synchronization method is called every SYN interval + connect(_synTimer, &QTimer::timeout, this, &Socket::rateControlSync); + + // start our timer for the synchronization time interval + _synTimer->start(_synInterval); +} + +void Socket::rebind() { + quint16 oldPort = _udpSocket.localPort(); + + _udpSocket.close(); + bind(QHostAddress::AnyIPv4, oldPort); +} + +void Socket::setSystemBufferSizes() { + for (int i = 0; i < 2; i++) { + QAbstractSocket::SocketOption bufferOpt; + QString bufferTypeString; + + int numBytes = 0; + + if (i == 0) { + bufferOpt = QAbstractSocket::SendBufferSizeSocketOption; + numBytes = udt::UDP_SEND_BUFFER_SIZE_BYTES; + bufferTypeString = "send"; + + } else { + bufferOpt = QAbstractSocket::ReceiveBufferSizeSocketOption; + numBytes = udt::UDP_RECEIVE_BUFFER_SIZE_BYTES; + bufferTypeString = "receive"; + } + + int oldBufferSize = _udpSocket.socketOption(bufferOpt).toInt(); + + if (oldBufferSize < numBytes) { + _udpSocket.setSocketOption(bufferOpt, QVariant(numBytes)); + int newBufferSize = _udpSocket.socketOption(bufferOpt).toInt(); + + qCDebug(networking) << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to" + << newBufferSize << "bytes"; + } else { + // don't make the buffer smaller + qCDebug(networking) << "Did not change socket" << bufferTypeString << "buffer size from" << oldBufferSize + << "since it is larger than desired size of" << numBytes; + } + } +} + +qint64 Socket::writeBasePacket(const udt::BasePacket& packet, const HifiSockAddr &sockAddr) { + // Since this is a base packet we have no way to know if this is reliable or not - we just fire it off + + // this should not be called with an instance of Packet + Q_ASSERT_X(!dynamic_cast(&packet), + "Socket::writeBasePacket", "Cannot send a Packet/NLPacket via writeBasePacket"); + + return writeDatagram(packet.getData(), packet.getDataSize(), sockAddr); +} + +qint64 Socket::writePacket(const Packet& packet, const HifiSockAddr& sockAddr) { + Q_ASSERT_X(!packet.isReliable(), "Socket::writePacket", "Cannot send a reliable packet unreliably"); + + // write the correct sequence number to the Packet here + packet.writeSequenceNumber(++_unreliableSequenceNumbers[sockAddr]); + + return writeDatagram(packet.getData(), packet.getDataSize(), sockAddr); +} + +qint64 Socket::writePacket(std::unique_ptr packet, const HifiSockAddr& sockAddr) { + + if (packet->isReliable()) { + // hand this packet off to writeReliablePacket + // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacket + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "writeReliablePacket", Qt::QueuedConnection, + Q_ARG(Packet*, packet.release()), + Q_ARG(HifiSockAddr, sockAddr)); + } else { + writeReliablePacket(packet.release(), sockAddr); + } + + return 0; + } + + return writePacket(*packet, sockAddr); +} + +qint64 Socket::writePacketList(std::unique_ptr packetList, const HifiSockAddr& sockAddr) { + if (packetList->isReliable()) { + // hand this packetList off to writeReliablePacketList + // because Qt can't invoke with the unique_ptr we have to release it here and re-construct in writeReliablePacketList + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "writeReliablePacketList", Qt::QueuedConnection, + Q_ARG(PacketList*, packetList.release()), + Q_ARG(HifiSockAddr, sockAddr)); + } else { + writeReliablePacketList(packetList.release(), sockAddr); + } + + return 0; + } + + // Unerliable and Unordered + qint64 totalBytesSent = 0; + while (!packetList->_packets.empty()) { + totalBytesSent += writePacket(packetList->takeFront(), sockAddr); + } + + return totalBytesSent; +} + +void Socket::writeReliablePacket(Packet* packet, const HifiSockAddr& sockAddr) { + findOrCreateConnection(sockAddr).sendReliablePacket(std::unique_ptr(packet)); +} + +void Socket::writeReliablePacketList(PacketList* packetList, const HifiSockAddr& sockAddr) { + findOrCreateConnection(sockAddr).sendReliablePacketList(std::unique_ptr(packetList)); +} + +qint64 Socket::writeDatagram(const char* data, qint64 size, const HifiSockAddr& sockAddr) { + return writeDatagram(QByteArray::fromRawData(data, size), sockAddr); +} + +qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& sockAddr) { + + qint64 bytesWritten = _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort()); + + if (bytesWritten < 0) { + // when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug + static const QString WRITE_ERROR_REGEX = "Socket::writeDatagram QAbstractSocket::NetworkError - Unable to send a message"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex(WRITE_ERROR_REGEX); + + qCDebug(networking) << "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()); + } + + return bytesWritten; +} + +Connection& Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { + auto it = _connectionsHash.find(sockAddr); + + if (it == _connectionsHash.end()) { + auto connection = std::unique_ptr(new Connection(this, sockAddr, _ccFactory->create())); + + // we queue the connection to cleanup connection in case it asks for it during its own rate control sync + QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection, + Qt::QueuedConnection); + + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Creating new connection to" << sockAddr; + #endif + + it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection))); + } + + return *it->second; +} + +void Socket::clearConnections() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "clearConnections", Qt::BlockingQueuedConnection); + return; + } + + // clear all of the current connections in the socket + qDebug() << "Clearing all remaining connections in Socket."; + _connectionsHash.clear(); +} + +void Socket::cleanupConnection(HifiSockAddr sockAddr) { + #ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Socket::cleanupConnection called for UDT connection to" << sockAddr; + #endif + + _connectionsHash.erase(sockAddr); +} + +void Socket::messageReceived(std::unique_ptr packetList) { + if (_packetListHandler) { + _packetListHandler(std::move(packetList)); + } +} + +void Socket::readPendingDatagrams() { + int packetSizeWithHeader = -1; + while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { + // setup a HifiSockAddr to read into + HifiSockAddr senderSockAddr; + + // setup a buffer to read the packet into + auto buffer = std::unique_ptr(new char[packetSizeWithHeader]); + + // pull the datagram + _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader, + senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + + auto it = _unfilteredHandlers.find(senderSockAddr); + + if (it != _unfilteredHandlers.end()) { + // we have a registered unfiltered handler for this HifiSockAddr - call that and return + if (it->second) { + auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + it->second(std::move(basePacket)); + } + + return; + } + + // check if this was a control packet or a data packet + bool isControlPacket = *reinterpret_cast(buffer.get()) & CONTROL_BIT_MASK; + + if (isControlPacket) { + // setup a control packet from the data we just read + auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + + // move this control packet to the matching connection + auto& connection = findOrCreateConnection(senderSockAddr); + connection.processControl(move(controlPacket)); + + } else { + // setup a Packet from the data we just read + auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + + // call our verification operator to see if this packet is verified + if (!_packetFilterOperator || _packetFilterOperator(*packet)) { + if (packet->isReliable()) { + // if this was a reliable packet then signal the matching connection with the sequence number + auto& connection = findOrCreateConnection(senderSockAddr); + + if (!connection.processReceivedSequenceNumber(packet->getSequenceNumber(), + packet->getDataSize(), + packet->getPayloadSize())) { + // the connection indicated that we should not continue processing this packet + return; + } + } + + if (packet->isPartOfMessage()) { + auto& connection = findOrCreateConnection(senderSockAddr); + connection.queueReceivedMessagePacket(std::move(packet)); + } else if (_packetHandler) { + // call the verified packet callback to let it handle this packet + _packetHandler(std::move(packet)); + } + } + } + } +} + +void Socket::connectToSendSignal(const HifiSockAddr& destinationAddr, QObject* receiver, const char* slot) { + auto it = _connectionsHash.find(destinationAddr); + if (it != _connectionsHash.end()) { + connect(it->second.get(), SIGNAL(packetSent()), receiver, slot); + } +} + +void Socket::rateControlSync() { + + // enumerate our list of connections and ask each of them to send off periodic ACK packet for rate control + for (auto& connection : _connectionsHash) { + connection.second->sync(); + } + + if (_synTimer->interval() != _synInterval) { + // if the _synTimer interval doesn't match the current _synInterval (changes when the CC factory is changed) + // then restart it now with the right interval + _synTimer->start(_synInterval); + } +} + +void Socket::setCongestionControlFactory(std::unique_ptr ccFactory) { + // swap the current unique_ptr for the new factory + _ccFactory.swap(ccFactory); + + // update the _synInterval to the value from the factory + _synInterval = _ccFactory->synInterval(); +} + +ConnectionStats::Stats Socket::sampleStatsForConnection(const HifiSockAddr& destination) { + auto it = _connectionsHash.find(destination); + if (it != _connectionsHash.end()) { + return it->second->sampleStats(); + } else { + return ConnectionStats::Stats(); + } +} + +std::vector Socket::getConnectionSockAddrs() { + std::vector addr; + addr.reserve(_connectionsHash.size()); + + for (const auto& connectionPair : _connectionsHash) { + addr.push_back(connectionPair.first); + } + return addr; +} diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h new file mode 100644 index 0000000000..923248caad --- /dev/null +++ b/libraries/networking/src/udt/Socket.h @@ -0,0 +1,114 @@ +// +// Socket.h +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2015-07-20. +// 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 +// + +#pragma once + +#ifndef hifi_Socket_h +#define hifi_Socket_h + +#include +#include + +#include +#include +#include + +#include "../HifiSockAddr.h" +#include "CongestionControl.h" +#include "Connection.h" + +#define UDT_CONNECTION_DEBUG + +class UDTTest; + +namespace udt { + +class BasePacket; +class ControlSender; +class Packet; +class PacketList; +class SequenceNumber; + +using PacketFilterOperator = std::function; + +using BasePacketHandler = std::function)>; +using PacketHandler = std::function)>; +using PacketListHandler = std::function)>; + +class Socket : public QObject { + Q_OBJECT +public: + Socket(QObject* object = 0); + + quint16 localPort() const { return _udpSocket.localPort(); } + + // Simple functions writing to the socket with no processing + qint64 writeBasePacket(const BasePacket& packet, const HifiSockAddr& sockAddr); + qint64 writePacket(const Packet& packet, const HifiSockAddr& sockAddr); + qint64 writePacket(std::unique_ptr packet, const HifiSockAddr& sockAddr); + qint64 writePacketList(std::unique_ptr packetList, const HifiSockAddr& sockAddr); + qint64 writeDatagram(const char* data, qint64 size, const HifiSockAddr& sockAddr); + qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& sockAddr); + + void bind(const QHostAddress& address, quint16 port = 0) { _udpSocket.bind(address, port); setSystemBufferSizes(); } + void rebind(); + + void setPacketFilterOperator(PacketFilterOperator filterOperator) { _packetFilterOperator = filterOperator; } + void setPacketHandler(PacketHandler handler) { _packetHandler = handler; } + void setPacketListHandler(PacketListHandler handler) { _packetListHandler = handler; } + + void addUnfilteredHandler(const HifiSockAddr& senderSockAddr, BasePacketHandler handler) + { _unfilteredHandlers[senderSockAddr] = handler; } + + void setCongestionControlFactory(std::unique_ptr ccFactory); + + void messageReceived(std::unique_ptr packetList); + +public slots: + void cleanupConnection(HifiSockAddr sockAddr); + void clearConnections(); + +private slots: + void readPendingDatagrams(); + void rateControlSync(); + +private: + void setSystemBufferSizes(); + Connection& findOrCreateConnection(const HifiSockAddr& sockAddr); + + // privatized methods used by UDTTest - they are private since they must be called on the Socket thread + ConnectionStats::Stats sampleStatsForConnection(const HifiSockAddr& destination); + std::vector getConnectionSockAddrs(); + void connectToSendSignal(const HifiSockAddr& destinationAddr, QObject* receiver, const char* slot); + + Q_INVOKABLE void writeReliablePacket(Packet* packet, const HifiSockAddr& sockAddr); + Q_INVOKABLE void writeReliablePacketList(PacketList* packetList, const HifiSockAddr& sockAddr); + + QUdpSocket _udpSocket { this }; + PacketFilterOperator _packetFilterOperator; + PacketHandler _packetHandler; + PacketListHandler _packetListHandler; + + std::unordered_map _unfilteredHandlers; + std::unordered_map _unreliableSequenceNumbers; + std::unordered_map> _connectionsHash; + + int _synInterval = 10; // 10ms + QTimer* _synTimer; + + std::unique_ptr _ccFactory { new CongestionControlFactory() }; + + friend UDTTest; +}; + +} // namespace udt + +#endif // hifi_Socket_h diff --git a/libraries/networking/src/udt/udt.cpp b/libraries/networking/src/udt/udt.cpp deleted file mode 100644 index d580a9f7f8..0000000000 --- a/libraries/networking/src/udt/udt.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// udt.cpp -// -// -// Created by Clement on 7/13/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 "udt.h" diff --git a/libraries/networking/src/udt/udt.h b/libraries/networking/src/udt/udt.h deleted file mode 100644 index 74803151d1..0000000000 --- a/libraries/networking/src/udt/udt.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// udt.h -// -// -// Created by Clement on 7/13/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 -// - -#ifndef hifi_udt_h -#define hifi_udt_h - -#endif // hifi_udt_h \ No newline at end of file diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 93933c5301..54838ad019 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1910,7 +1910,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr bool wantImportProgress = true; - PacketType::Value expectedType = expectedDataPacketType(); + PacketType expectedType = expectedDataPacketType(); PacketVersion expectedVersion = versionForPacketType(expectedType); bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion); @@ -1918,7 +1918,7 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr if (getWantSVOfileVersions()) { // read just enough of the file to parse the header... - const unsigned long HEADER_LENGTH = sizeof(PacketType::Value) + sizeof(PacketVersion); + const unsigned long HEADER_LENGTH = sizeof(int) + sizeof(PacketVersion); unsigned char fileHeader[HEADER_LENGTH]; inputStream.readRawData((char*)&fileHeader, HEADER_LENGTH); @@ -1928,8 +1928,9 @@ bool Octree::readSVOFromStream(unsigned long streamLength, QDataStream& inputStr unsigned long dataLength = HEADER_LENGTH; // if so, read the first byte of the file and see if it matches the expected version code - PacketType::Value gotType; - memcpy(&gotType, dataAt, sizeof(gotType)); + int intPacketType; + memcpy(&intPacketType, dataAt, sizeof(intPacketType)); + PacketType gotType = (PacketType) intPacketType; dataAt += sizeof(expectedType); dataLength -= sizeof(expectedType); @@ -2088,7 +2089,7 @@ void Octree::writeToJSONFile(const char* fileName, OctreeElement* element, bool } // include the "bitstream" version - PacketType::Value expectedType = expectedDataPacketType(); + PacketType expectedType = expectedDataPacketType(); PacketVersion expectedVersion = versionForPacketType(expectedType); entityDescription["Version"] = (int) expectedVersion; @@ -2126,16 +2127,17 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* element) { if(file.is_open()) { qCDebug(octree, "Saving binary SVO to file %s...", fileName); - PacketType::Value expectedType = expectedDataPacketType(); - PacketVersion expectedVersion = versionForPacketType(expectedType); + PacketType expectedPacketType = expectedDataPacketType(); + int expectedIntType = (int) expectedPacketType; + PacketVersion expectedVersion = versionForPacketType(expectedPacketType); bool hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion); // before reading the file, check to see if this version of the Octree supports file versions if (getWantSVOfileVersions()) { // if so, read the first byte of the file and see if it matches the expected version code - file.write(reinterpret_cast(&expectedType), sizeof(expectedType)); + file.write(reinterpret_cast(&expectedIntType), sizeof(expectedIntType)); file.write(&expectedVersion, sizeof(expectedVersion)); - qCDebug(octree) << "SVO file type: " << nameForPacketType(expectedType) << " version: " << (int)expectedVersion; + qCDebug(octree) << "SVO file type: " << expectedPacketType << " version: " << (int)expectedVersion; hasBufferBreaks = versionHasSVOfileBreaks(expectedVersion); } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 0b3dc0027f..8651ce5622 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -228,11 +228,11 @@ public: // These methods will allow the OctreeServer to send your tree inbound edit packets of your // own definition. Implement these to allow your octree based server to support editing virtual bool getWantSVOfileVersions() const { return false; } - virtual PacketType::Value expectedDataPacketType() const { return PacketType::Unknown; } + virtual PacketType expectedDataPacketType() const { return PacketType::Unknown; } virtual bool canProcessVersion(PacketVersion thisVersion) const { return thisVersion == versionForPacketType(expectedDataPacketType()); } virtual PacketVersion expectedVersion() const { return versionForPacketType(expectedDataPacketType()); } - virtual bool handlesEditPacketType(PacketType::Value packetType) const { return false; } + virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(NLPacket& packet, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index e2e17a6606..0d67e2391d 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -194,7 +194,7 @@ void OctreeEditPacketSender::queuePacketToNodes(std::unique_ptr packet // NOTE: editMessage - is JUST the octcode/color and does not contain the packet header -void OctreeEditPacketSender::queueOctreeEditMessage(PacketType::Value type, QByteArray& editMessage) { +void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& editMessage) { if (!_shouldSend) { return; // bail early @@ -315,7 +315,7 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu _releaseQueuedPacketMutex.unlock(); } -std::unique_ptr OctreeEditPacketSender::initializePacket(PacketType::Value type, int nodeClockSkew) { +std::unique_ptr OctreeEditPacketSender::initializePacket(PacketType type, int nodeClockSkew) { auto newPacket = NLPacket::create(type); // skip over sequence number for now; will be packed when packet is ready to be sent out diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index 65d5315206..d8761e6161 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -12,6 +12,8 @@ #ifndef hifi_OctreeEditPacketSender_h #define hifi_OctreeEditPacketSender_h +#include + #include #include @@ -28,7 +30,7 @@ public: /// Queues a single edit message. Will potentially send a pending multi-command packet. Determines which server /// node or nodes the packet should be sent to. Can be called even before servers are known, in which case up to /// MaxPendingMessages will be buffered and processed when servers are known. - void queueOctreeEditMessage(PacketType::Value type, QByteArray& editMessage); + void queueOctreeEditMessage(PacketType type, QByteArray& editMessage); /// Releases all queued messages even if those messages haven't filled an MTU packet. This will move the packed message /// packets onto the send queue. If running in threaded mode, the caller does not need to do any further processing to @@ -73,7 +75,7 @@ public: // you must override these... virtual char getMyNodeType() const = 0; - virtual void adjustEditPacketForClockSkew(PacketType::Value type, QByteArray& buffer, int clockSkew) { } + virtual void adjustEditPacketForClockSkew(PacketType type, QByteArray& buffer, int clockSkew) { } void processNackPacket(NLPacket& packet, SharedNodePointer sendingNode); @@ -81,13 +83,13 @@ public slots: void nodeKilled(SharedNodePointer node); protected: - using EditMessagePair = std::pair; + using EditMessagePair = std::pair; bool _shouldSend; void queuePacketToNode(const QUuid& nodeID, std::unique_ptr packet); void queuePendingPacketToNodes(std::unique_ptr packet); void queuePacketToNodes(std::unique_ptr packet); - std::unique_ptr initializePacket(PacketType::Value type, int nodeClockSkew); + std::unique_ptr initializePacket(PacketType type, int nodeClockSkew); void releaseQueuedPacket(const QUuid& nodeUUID, std::unique_ptr packetBuffer); // releases specific queued packet void processPreServerExistsPackets(); diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 5714ddcb03..ca50fc001e 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -33,7 +33,7 @@ void OctreeHeadlessViewer::init() { void OctreeHeadlessViewer::queryOctree() { char serverType = getMyNodeType(); - PacketType::Value packetType = getMyQueryMessageType(); + PacketType packetType = getMyQueryMessageType(); NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions(); bool wantExtraDebugging = false; diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 1a396210cb..44737d37da 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -26,11 +26,11 @@ #include #include -#include // for MAX_PACKET_SIZE -#include // for MAX_PACKET_HEADER_BYTES +#include #include #include -#include +#include +#include #include "OctreeConstants.h" #include "OctreeElement.h" @@ -40,14 +40,13 @@ typedef uint16_t OCTREE_PACKET_SEQUENCE; const uint16_t MAX_OCTREE_PACKET_SEQUENCE = 65535; typedef quint64 OCTREE_PACKET_SENT_TIME; typedef uint16_t OCTREE_PACKET_INTERNAL_SECTION_SIZE; -const int MAX_OCTREE_PACKET_SIZE = MAX_PACKET_SIZE; +const int MAX_OCTREE_PACKET_SIZE = udt::MAX_PACKET_SIZE; -// this is overly conservative - sizeof(PacketType) is 8 bytes but a packed PacketType::Value could be as small as one byte const unsigned int OCTREE_PACKET_EXTRA_HEADERS_SIZE = sizeof(OCTREE_PACKET_FLAGS) + sizeof(OCTREE_PACKET_SEQUENCE) + sizeof(OCTREE_PACKET_SENT_TIME); -const unsigned int MAX_OCTREE_PACKET_DATA_SIZE = MAX_PACKET_SIZE - (MAX_PACKET_HEADER_BYTES + OCTREE_PACKET_EXTRA_HEADERS_SIZE); - +const unsigned int MAX_OCTREE_PACKET_DATA_SIZE = + udt::MAX_PACKET_SIZE - (NLPacket::MAX_PACKET_HEADER_SIZE + OCTREE_PACKET_EXTRA_HEADERS_SIZE); const unsigned int MAX_OCTREE_UNCOMRESSED_PACKET_SIZE = MAX_OCTREE_PACKET_DATA_SIZE; const unsigned int MINIMUM_ATTEMPT_MORE_PACKING = sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) + 40; diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 85a193a3e6..032551d13e 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -36,8 +36,8 @@ public: virtual ~OctreeRenderer(); virtual char getMyNodeType() const = 0; - virtual PacketType::Value getMyQueryMessageType() const = 0; - virtual PacketType::Value getExpectedPacketType() const = 0; + virtual PacketType getMyQueryMessageType() const = 0; + virtual PacketType getExpectedPacketType() const = 0; virtual void renderElement(OctreeElement* element, RenderArgs* args) = 0; virtual float getSizeScale() const { return DEFAULT_OCTREE_SIZE_SCALE; } virtual int getBoundaryLevelAdjust() const { return 0; } diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index 55213fdb7a..d83ee9a89e 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -791,6 +791,6 @@ void OctreeSceneStats::trackIncomingOctreePacket(NLPacket& packet, bool wasStats _incomingPacket++; _incomingBytes += packet.getDataSize(); if (!wasStatsPacket) { - _incomingWastedBytes += (MAX_PACKET_SIZE - packet.getDataSize()); + _incomingWastedBytes += (udt::MAX_PACKET_SIZE - packet.getDataSize()); } } diff --git a/libraries/procedural/CMakeLists.txt b/libraries/procedural/CMakeLists.txt new file mode 100644 index 0000000000..bd53f0abb9 --- /dev/null +++ b/libraries/procedural/CMakeLists.txt @@ -0,0 +1,10 @@ +set(TARGET_NAME procedural) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library() + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + +link_hifi_libraries(shared gpu networking gpu-networking) diff --git a/libraries/entities-renderer/src/RenderableProceduralItem.cpp b/libraries/procedural/src/procedural/Procedural.cpp similarity index 65% rename from libraries/entities-renderer/src/RenderableProceduralItem.cpp rename to libraries/procedural/src/procedural/Procedural.cpp index c88d1410a5..7224704e7d 100644 --- a/libraries/entities-renderer/src/RenderableProceduralItem.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "RenderableProceduralItem.h" +#include "Procedural.h" #include #include @@ -14,14 +14,12 @@ #include #include -#include -#include -#include -#include +#include #include +#include +#include -#include "RenderableProceduralItemShader.h" -#include "../render-utils/simple_vert.h" +#include "ProceduralShaders.h" static const char* const UNIFORM_TIME_NAME= "iGlobalTime"; static const char* const UNIFORM_SCALE_NAME = "iWorldScale"; @@ -30,43 +28,46 @@ static const QString PROCEDURAL_USER_DATA_KEY = "ProceduralEntity"; static const QString URL_KEY = "shaderUrl"; static const QString VERSION_KEY = "version"; static const QString UNIFORMS_KEY = "uniforms"; +static const std::string PROCEDURAL_BLOCK = "//PROCEDURAL_BLOCK"; +static const std::string PROCEDURAL_COMMON_BLOCK = "//PROCEDURAL_COMMON_BLOCK"; +static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; -RenderableProceduralItem::ProceduralInfo::ProceduralInfo(EntityItem* entity) : _entity(entity) { - parse(); + +// Example +//{ +// "ProceduralEntity": { +// "shaderUrl": "file:///C:/Users/bdavis/Git/hifi/examples/shaders/test.fs", +// } +//} +QJsonValue Procedural::getProceduralData(const QString& proceduralJson) { + if (proceduralJson.isEmpty()) { + return QJsonValue(); + } + + QJsonParseError parseError; + auto doc = QJsonDocument::fromJson(proceduralJson.toUtf8(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + return QJsonValue(); + } + + return doc.object()[PROCEDURAL_USER_DATA_KEY]; } -void RenderableProceduralItem::ProceduralInfo::parse() { + +Procedural::Procedural(const QString& userDataJson) { + parse(userDataJson); + _state = std::make_shared(); +} + +void Procedural::parse(const QString& userDataJson) { _enabled = false; - QJsonObject userData; - { - const QString& userDataJson = _entity->getUserData(); - if (userDataJson.isEmpty()) { - return; - } - QJsonParseError parseError; - auto doc = QJsonDocument::fromJson(userDataJson.toUtf8(), &parseError); - if (parseError.error != QJsonParseError::NoError) { - return; - } - userData = doc.object(); + auto proceduralData = getProceduralData(userDataJson); + if (proceduralData.isObject()) { + parse(proceduralData.toObject()); } - - // Example - //{ - // "ProceduralEntity": { - // "shaderUrl": "file:///C:/Users/bdavis/Git/hifi/examples/shaders/test.fs", - // "color" : "#FFFFFF" - // } - //} - auto proceduralData = userData[PROCEDURAL_USER_DATA_KEY]; - if (proceduralData.isNull()) { - return; - } - - parse(proceduralData.toObject()); } -void RenderableProceduralItem::ProceduralInfo::parse(const QJsonObject& proceduralData) { +void Procedural::parse(const QJsonObject& proceduralData) { // grab the version number { auto version = proceduralData[VERSION_KEY]; @@ -106,7 +107,7 @@ void RenderableProceduralItem::ProceduralInfo::parse(const QJsonObject& procedur _enabled = true; } -bool RenderableProceduralItem::ProceduralInfo::ready() { +bool Procedural::ready() { if (!_enabled) { return false; } @@ -122,9 +123,9 @@ bool RenderableProceduralItem::ProceduralInfo::ready() { return false; } -void RenderableProceduralItem::ProceduralInfo::prepare(gpu::Batch& batch) { +void Procedural::prepare(gpu::Batch& batch, const glm::vec3& size) { if (_shaderUrl.isLocalFile()) { - auto lastModified = QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); + auto lastModified = (quint64) QFileInfo(_shaderPath).lastModified().toMSecsSinceEpoch(); if (lastModified > _shaderModified) { QFile file(_shaderPath); file.open(QIODevice::ReadOnly); @@ -139,31 +140,33 @@ void RenderableProceduralItem::ProceduralInfo::prepare(gpu::Batch& batch) { if (!_pipeline || _pipelineDirty) { _pipelineDirty = true; if (!_vertexShader) { - _vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(simple_vert))); + _vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(_vertexSource)); } - QString framentShaderSource; - switch (_version) { - case 1: - framentShaderSource = SHADER_TEMPLATE_V1.arg(_shaderSource); - break; - default: - case 2: - framentShaderSource = SHADER_TEMPLATE_V2.arg(_shaderSource); - break; + // Build the fragment shader + std::string fragmentShaderSource = _fragmentSource; + size_t replaceIndex = fragmentShaderSource.find(PROCEDURAL_COMMON_BLOCK); + if (replaceIndex != std::string::npos) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_COMMON_BLOCK.size(), SHADER_COMMON); } - _fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(framentShaderSource.toLocal8Bit().data()))); + + replaceIndex = fragmentShaderSource.find(PROCEDURAL_VERSION); + if (replaceIndex != std::string::npos) { + if (_version == 1) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V1 1"); + } else if (_version == 2) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_VERSION.size(), "#define PROCEDURAL_V2 1"); + } + } + replaceIndex = fragmentShaderSource.find(PROCEDURAL_BLOCK); + if (replaceIndex != std::string::npos) { + fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); + } + qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); + _fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSource)); _shader = gpu::ShaderPointer(gpu::Shader::createProgram(_vertexShader, _fragmentShader)); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), DeferredLightingEffect::NORMAL_FITTING_MAP_SLOT)); - gpu::Shader::makeProgram(*_shader, slotBindings); - auto state = std::make_shared(); - state->setCullMode(gpu::State::CULL_NONE); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(_shader, state)); + gpu::Shader::makeProgram(*_shader); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(_shader, _state)); _timeSlot = _shader->getUniforms().findLocation(UNIFORM_TIME_NAME); _scaleSlot = _shader->getUniforms().findLocation(UNIFORM_SCALE_NAME); _start = usecTimestampNow(); @@ -221,15 +224,12 @@ void RenderableProceduralItem::ProceduralInfo::prepare(gpu::Batch& batch) { // Minimize floating point error by doing an integer division to milliseconds, before the floating point division to seconds float time = (float)((usecTimestampNow() - _start) / USECS_PER_MSEC) / MSECS_PER_SECOND; batch._glUniform1f(_timeSlot, time); - // FIXME move into the 'set once' section, since this doesn't change over time - auto scale = _entity->getDimensions(); - batch._glUniform3f(_scaleSlot, scale.x, scale.y, scale.z); - batch.setResourceTexture(DeferredLightingEffect::NORMAL_FITTING_MAP_SLOT, DependencyManager::get()->getNormalFittingTexture()); + batch._glUniform3f(_scaleSlot, size.x, size.y, size.z); } -glm::vec4 RenderableProceduralItem::ProceduralInfo::getColor(const glm::vec4& entityColor) { +glm::vec4 Procedural::getColor(const glm::vec4& entityColor) { if (_version == 1) { return glm::vec4(1); } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h new file mode 100644 index 0000000000..bb6a0ad44d --- /dev/null +++ b/libraries/procedural/src/procedural/Procedural.h @@ -0,0 +1,60 @@ +// +// Created by Bradley Austin Davis on 2015/09/05 +// Copyright 2013-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 +// + +#pragma once +#ifndef hifi_RenderableProcedrualItem_h +#define hifi_RenderableProcedrualItem_h + +#include +#include +#include +#include + +#include +#include +#include +#include + + +// FIXME better encapsulation +// FIXME better mechanism for extending to things rendered using shaders other than simple.slv +struct Procedural { + static QJsonValue getProceduralData(const QString& proceduralJson); + + Procedural(const QString& userDataJson); + void parse(const QString& userDataJson); + void parse(const QJsonObject&); + bool ready(); + void prepare(gpu::Batch& batch, const glm::vec3& size); + glm::vec4 getColor(const glm::vec4& entityColor); + + bool _enabled{ false }; + uint8_t _version{ 1 }; + + std::string _vertexSource; + std::string _fragmentSource; + + QString _shaderSource; + QString _shaderPath; + QUrl _shaderUrl; + quint64 _shaderModified{ 0 }; + bool _pipelineDirty{ true }; + int32_t _timeSlot{ gpu::Shader::INVALID_LOCATION }; + int32_t _scaleSlot{ gpu::Shader::INVALID_LOCATION }; + uint64_t _start{ 0 }; + NetworkShaderPointer _networkShader; + QJsonObject _uniforms; + + gpu::PipelinePointer _pipeline; + gpu::ShaderPointer _vertexShader; + gpu::ShaderPointer _fragmentShader; + gpu::ShaderPointer _shader; + gpu::StatePointer _state; +}; + +#endif diff --git a/libraries/procedural/src/procedural/ProceduralShaders.h b/libraries/procedural/src/procedural/ProceduralShaders.h new file mode 100644 index 0000000000..9943a322cc --- /dev/null +++ b/libraries/procedural/src/procedural/ProceduralShaders.h @@ -0,0 +1,276 @@ +// +// Created by Bradley Austin Davis on 2015/09/05 +// Copyright 2013-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 +// + +// Shader includes portions of webgl-noise: +// Description : Array and textureless GLSL 2D/3D/4D simplex +// noise functions. +// Author : Ian McEwan, Ashima Arts. +// Maintainer : ijm +// Lastmod : 20110822 (ijm) +// License : Copyright (C) 2011 Ashima Arts. All rights reserved. +// Distributed under the MIT License. See LICENSE file. +// https://github.com/ashima/webgl-noise +// + + +const std::string SHADER_COMMON = R"SHADER( + +float mod289(float x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 mod289(vec4 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +float permute(float x) { + return mod289(((x*34.0)+1.0)*x); +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +vec4 permute(vec4 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float taylorInvSqrt(float r) { + return 1.79284291400159 - 0.85373472095314 * r; +} + +vec4 taylorInvSqrt(vec4 r) { + return 1.79284291400159 - 0.85373472095314 * r; +} + +vec4 grad4(float j, vec4 ip) { + const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); + vec4 p, s; + + p.xyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; + p.w = 1.5 - dot(abs(p.xyz), ones.xyz); + s = vec4(lessThan(p, vec4(0.0))); + p.xyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; + + return p; +} + +// (sqrt(5) - 1)/4 = F4, used once below +#define F4 0.309016994374947451 + +float snoise(vec4 v) { + const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4 + 0.276393202250021, // 2 * G4 + 0.414589803375032, // 3 * G4 + -0.447213595499958); // -1 + 4 * G4 + + // First corner + vec4 i = floor(v + dot(v, vec4(F4))); + vec4 x0 = v - i + dot(i, C.xxxx); + + // Other corners + + // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) + vec4 i0; + vec3 isX = step(x0.yzw, x0.xxx); + vec3 isYZ = step(x0.zww, x0.yyz); + i0.x = isX.x + isX.y + isX.z; + i0.yzw = 1.0 - isX; + i0.y += isYZ.x + isYZ.y; + i0.zw += 1.0 - isYZ.xy; + i0.z += isYZ.z; + i0.w += 1.0 - isYZ.z; + + // i0 now contains the unique values 0,1,2,3 in each channel + vec4 i3 = clamp(i0, 0.0, 1.0); + vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0); + vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0); + + vec4 x1 = x0 - i1 + C.xxxx; + vec4 x2 = x0 - i2 + C.yyyy; + vec4 x3 = x0 - i3 + C.zzzz; + vec4 x4 = x0 + C.wwww; + + // Permutations + i = mod289(i); + float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x); + vec4 j1 = permute( + permute( + permute( + permute(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) + i.z + + vec4(i1.z, i2.z, i3.z, 1.0)) + i.y + + vec4(i1.y, i2.y, i3.y, 1.0)) + i.x + + vec4(i1.x, i2.x, i3.x, 1.0)); + + // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope + // 7*7*6 = 294, which is close to the ring size 17*17 = 289. + vec4 ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); + + vec4 p0 = grad4(j0, ip); + vec4 p1 = grad4(j1.x, ip); + vec4 p2 = grad4(j1.y, ip); + vec4 p3 = grad4(j1.z, ip); + vec4 p4 = grad4(j1.w, ip); + + // Normalise gradients + vec4 norm = taylorInvSqrt( + vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + p4 *= taylorInvSqrt(dot(p4, p4)); + + // Mix contributions from the five corners + vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); + vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); + m0 = m0 * m0; + m1 = m1 * m1; + return 49.0 + * (dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) + + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4)))); + +} + +float snoise(vec3 v) { + const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + + // Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + i = mod289(i); + vec4 p = permute( + permute( + permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + float n_ = 0.142857142857; // 1.0/7.0 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) + + vec4 x = x_ * ns.x + ns.yyyy; + vec4 y = y_ * ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + + //Normalise gradients + vec4 norm = taylorInvSqrt( + vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + + // Mix final noise value + vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), + 0.0); + m = m * m; + return 42.0 + * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); +} + +float snoise(vec2 v) { + const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 + 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) + -0.577350269189626, // -1.0 + 2.0 * C.x + 0.024390243902439); // 1.0 / 41.0 + // First corner + vec2 i = floor(v + dot(v, C.yy)); + vec2 x0 = v - i + dot(i, C.xx); + + // Other corners + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + // Permutations + i = mod289(i); // Avoid truncation effects in permutation + vec3 p = permute( + permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); + + vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), + 0.0); + m = m * m; + m = m * m; + + // Gradients: 41 points uniformly over a line, mapped onto a diamond. + // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + // Normalise gradients implicitly by scaling m + // Approximation of: m *= inversesqrt( a0*a0 + h*h ); + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + // Compute final noise value at P + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +// TODO add more uniforms +uniform float iGlobalTime; // shader playback time (in seconds) +uniform vec3 iWorldScale; // the dimensions of the object being rendered + +// TODO add support for textures +// TODO document available inputs other than the uniforms +// TODO provide world scale in addition to the untransformed position + +#define PROCEDURAL 1 + +//PROCEDURAL_VERSION +)SHADER"; diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 0ea71e54e3..4d33c6f1c1 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -40,4 +40,4 @@ add_dependency_external_projects(oglplus) find_package(OGLPLUS REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) -link_hifi_libraries(animation fbx shared gpu model render environment) +link_hifi_libraries(shared gpu gpu-networking procedural model render environment animation fbx) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 2f81fe8b84..a58df10cc6 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1678,15 +1678,15 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { } } -GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) : +GeometryReader::GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping) : _url(url), - _reply(reply), + _data(data), _mapping(mapping) { } void GeometryReader::run() { try { - if (!_reply) { + if (_data.isEmpty()) { throw QString("Reply is NULL ?!"); } QString urlname = _url.path().toLower(); @@ -1701,9 +1701,9 @@ void GeometryReader::run() { if (_url.path().toLower().endsWith(".fbx")) { const bool grabLightmaps = true; const float lightmapLevel = 1.0f; - fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel); + fbxgeo = readFBX(_data, _mapping, _url.path(), grabLightmaps, lightmapLevel); } else if (_url.path().toLower().endsWith(".obj")) { - fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url); + fbxgeo = OBJReader().readOBJ(_data, _mapping, _url); } else { QString errorStr("usupported format"); emit onError(NetworkGeometry::ModelParseError, errorStr); @@ -1717,7 +1717,6 @@ void GeometryReader::run() { qCDebug(renderutils) << "Error reading " << _url << ": " << error; emit onError(NetworkGeometry::ModelParseError, error); } - _reply->deleteLater(); } NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : @@ -1746,8 +1745,10 @@ void NetworkGeometry::attemptRequest() { void NetworkGeometry::attemptRequestInternal() { if (_url.path().toLower().endsWith(".fst")) { + _mappingUrl = _url; requestMapping(_url); } else { + _modelUrl = _url; requestModel(_url); } } @@ -1838,8 +1839,8 @@ void NetworkGeometry::requestMapping(const QUrl& url) { _resource->deleteLater(); } _resource = new Resource(url, false); - connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&))); - connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError))); + connect(_resource, &Resource::loaded, this, &NetworkGeometry::mappingRequestDone); + connect(_resource, &Resource::failed, this, &NetworkGeometry::mappingRequestError); } void NetworkGeometry::requestModel(const QUrl& url) { @@ -1847,18 +1848,19 @@ void NetworkGeometry::requestModel(const QUrl& url) { if (_resource) { _resource->deleteLater(); } + _modelUrl = url; _resource = new Resource(url, false); - connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&))); - connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError))); + connect(_resource, &Resource::loaded, this, &NetworkGeometry::modelRequestDone); + connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError); } -void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) { +void NetworkGeometry::mappingRequestDone(const QByteArray& data) { assert(_state == RequestMappingState); // parse the mapping file - _mapping = FSTReader::readMapping(reply.readAll()); + _mapping = FSTReader::readMapping(data); - QUrl replyUrl = reply.url(); + QUrl replyUrl = _mappingUrl; QString modelUrlStr = _mapping.value("filename").toString(); if (modelUrlStr.isNull()) { qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry"; @@ -1873,8 +1875,8 @@ void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) { _textureBaseUrl = replyUrl.resolved(texdir); } - QUrl modelUrl = replyUrl.resolved(modelUrlStr); - requestModel(modelUrl); + _modelUrl = replyUrl.resolved(modelUrlStr); + requestModel(_modelUrl); } } @@ -1884,13 +1886,13 @@ void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) { emit onFailure(*this, MappingRequestError); } -void NetworkGeometry::modelRequestDone(QNetworkReply& reply) { +void NetworkGeometry::modelRequestDone(const QByteArray& data) { assert(_state == RequestModelState); _state = ParsingModelState; // asynchronously parse the model file. - GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping); + GeometryReader* geometryReader = new GeometryReader(_modelUrl, data, _mapping); connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*))); connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString))); diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 71fa35c054..3820b58baf 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -348,10 +348,10 @@ signals: void onFailure(NetworkGeometry& networkGeometry, Error error); protected slots: - void mappingRequestDone(QNetworkReply& reply); + void mappingRequestDone(const QByteArray& data); void mappingRequestError(QNetworkReply::NetworkError error); - void modelRequestDone(QNetworkReply& reply); + void modelRequestDone(const QByteArray& data); void modelRequestError(QNetworkReply::NetworkError error); void modelParseSuccess(FBXGeometry* geometry); @@ -371,6 +371,8 @@ protected: State _state; QUrl _url; + QUrl _mappingUrl; + QUrl _modelUrl; QVariantHash _mapping; QUrl _textureBaseUrl; @@ -386,14 +388,14 @@ protected: class GeometryReader : public QObject, public QRunnable { Q_OBJECT public: - GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping); + GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping); virtual void run(); signals: void onSuccess(FBXGeometry* geometry); void onError(int error, QString str); private: QUrl _url; - QNetworkReply* _reply; + QByteArray _data; QVariantHash _mapping; }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 4c56556118..fb26286f6a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -72,7 +72,6 @@ Model::Model(RigPointer rig, QObject* parent) : _cauterizeBones(false), _pupilDilation(0.0f), _url(HTTP_INVALID_COM), - _urlAsString(HTTP_INVALID_COM), _isVisible(true), _blendNumber(0), _appliedBlendNumber(0), @@ -1037,14 +1036,12 @@ int Model::getLastFreeJointIndex(int jointIndex) const { } void Model::setURL(const QUrl& url) { - // don't recreate the geometry if it's the same URL if (_url == url && _geometry && _geometry->getURL() == url) { return; } _url = url; - _urlAsString = _url.toString(); { render::PendingChanges pendingChanges; diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 91b6b4c83b..348e5cf549 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -69,7 +69,6 @@ public: /// Sets the URL of the model to render. Q_INVOKABLE void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } - const QString& getURLAsString() const { return _urlAsString; } // new Scene/Engine rendering support void setVisibleInScene(bool newValue, std::shared_ptr scene); @@ -322,7 +321,6 @@ private: QVector _blendshapeCoefficients; QUrl _url; - QString _urlAsString; QUrl _collisionUrl; bool _isVisible; diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index eeb17f07b9..23ac11d7b0 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -1,171 +1,2 @@ -// -// TextureCache.h -// interface/src/renderer -// -// Created by Andrzej Kapolka on 8/6/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_TextureCache_h -#define hifi_TextureCache_h - -#include -#include - -#include -#include -#include - -#include -#include - -namespace gpu { -class Batch; -} -class NetworkTexture; - -typedef QSharedPointer NetworkTexturePointer; - -enum TextureType { DEFAULT_TEXTURE, NORMAL_TEXTURE, SPECULAR_TEXTURE, EMISSIVE_TEXTURE, SPLAT_TEXTURE, CUBE_TEXTURE }; - -/// Stores cached textures, including render-to-texture targets. -class TextureCache : public ResourceCache, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - /// Returns the ID of the permutation/normal texture used for Perlin noise shader programs. This texture - /// has two lines: the first, a set of random numbers in [0, 255] to be used as permutation offsets, and - /// the second, a set of random unit vectors to be used as noise gradients. - const gpu::TexturePointer& getPermutationNormalTexture(); - - /// Returns an opaque white texture (useful for a default). - const gpu::TexturePointer& getWhiteTexture(); - - /// Returns an opaque gray texture (useful for a default). - const gpu::TexturePointer& getGrayTexture(); - - /// Returns the a pale blue texture (useful for a normal map). - const gpu::TexturePointer& getBlueTexture(); - - /// Returns the a black texture (useful for a default). - const gpu::TexturePointer& getBlackTexture(); - - // Returns a map used to compress the normals through a fitting scale algorithm - const gpu::TexturePointer& getNormalFittingTexture(); - - /// Returns a texture version of an image file - static gpu::TexturePointer getImageTexture(const QString& path); - - /// Loads a texture from the specified URL. - NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false, - const QByteArray& content = QByteArray()); - -protected: - - virtual QSharedPointer createResource(const QUrl& url, - const QSharedPointer& fallback, bool delayLoad, const void* extra); - -private: - TextureCache(); - virtual ~TextureCache(); - friend class DilatableNetworkTexture; - - gpu::TexturePointer _permutationNormalTexture; - gpu::TexturePointer _whiteTexture; - gpu::TexturePointer _grayTexture; - gpu::TexturePointer _blueTexture; - gpu::TexturePointer _blackTexture; - gpu::TexturePointer _normalFittingTexture; - - QHash > _dilatableNetworkTextures; -}; - -/// A simple object wrapper for an OpenGL texture. -class Texture { -public: - friend class TextureCache; - friend class DilatableNetworkTexture; - Texture(); - ~Texture(); - - const gpu::TexturePointer& getGPUTexture() const { return _gpuTexture; } - -protected: - gpu::TexturePointer _gpuTexture; - -private: -}; - -/// A texture loaded from the network. - -class NetworkTexture : public Resource, public Texture { - Q_OBJECT - -public: - - NetworkTexture(const QUrl& url, TextureType type, const QByteArray& content); - - /// Checks whether it "looks like" this texture is translucent - /// (majority of pixels neither fully opaque or fully transparent). - bool isTranslucent() const { return _translucent; } - - /// Returns the lazily-computed average texture color. - const QColor& getAverageColor() const { return _averageColor; } - - int getOriginalWidth() const { return _originalWidth; } - int getOriginalHeight() const { return _originalHeight; } - int getWidth() const { return _width; } - int getHeight() const { return _height; } - TextureType getType() const { return _type; } -protected: - - virtual void downloadFinished(QNetworkReply* reply); - - Q_INVOKABLE void loadContent(const QByteArray& content); - // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... - Q_INVOKABLE void setImage(const QImage& image, void* texture, bool translucent, const QColor& averageColor, int originalWidth, - int originalHeight); - - virtual void imageLoaded(const QImage& image); - - TextureType _type; - -private: - bool _translucent; - QColor _averageColor; - int _originalWidth; - int _originalHeight; - int _width; - int _height; -}; - -/// Caches derived, dilated textures. -class DilatableNetworkTexture : public NetworkTexture { - Q_OBJECT - -public: - - DilatableNetworkTexture(const QUrl& url, const QByteArray& content); - - /// Returns a pointer to a texture with the requested amount of dilation. - QSharedPointer getDilatedTexture(float dilation); - -protected: - - virtual void imageLoaded(const QImage& image); - virtual void reinsert(); - -private: - - QImage _image; - int _innerRadius; - int _outerRadius; - - QMap > _dilatedTextures; -}; - -#endif // hifi_TextureCache_h +// Compatibility +#include diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 31d33a73e4..5901a72838 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -18,12 +18,39 @@ // the interpolated normal in vec3 _normal; in vec3 _color; +in vec2 _texCoord0; +in vec4 _position; +//PROCEDURAL_COMMON_BLOCK + +#line 1001 +//PROCEDURAL_BLOCK + +#line 2030 void main(void) { Material material = getMaterial(); - packDeferredFragment( - normalize(_normal.xyz), - glowIntensity, - _color.rgb, - DEFAULT_SPECULAR, DEFAULT_SHININESS); + vec3 normal = normalize(_normal.xyz); + vec3 diffuse = _color.rgb; + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; + float emissiveAmount = 0.0; + +#ifdef PROCEDURAL + +#ifdef PROCEDURAL_V1 + specular = getProceduralColor().rgb; + emissiveAmount = 1.0; +#else + emissiveAmount = getProceduralColors(diffuse, specular, shininess); +#endif + +#endif + + if (emissiveAmount > 0.0) { + packDeferredFragmentLightmap( + normal, glowIntensity, diffuse, specular, shininess, specular); + } else { + packDeferredFragment( + normal, glowIntensity, diffuse, specular, shininess); + } } diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 139b99e426..1acfb57829 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -7,4 +7,4 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -link_hifi_libraries(shared octree gpu model fbx entities animation audio physics) +link_hifi_libraries(shared networking octree gpu gpu-networking procedural model fbx entities animation audio physics) diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp index ac0cafa6a9..ad3f422813 100644 --- a/libraries/script-engine/src/BatchLoader.cpp +++ b/libraries/script-engine/src/BatchLoader.cpp @@ -17,9 +17,7 @@ #include "BatchLoader.h" #include #include - - - +#include "ResourceManager.h" BatchLoader::BatchLoader(const QList& urls) : QObject(), @@ -27,6 +25,7 @@ BatchLoader::BatchLoader(const QList& urls) _finished(false), _urls(urls.toSet()), _data() { + qRegisterMetaType>("QMap"); } void BatchLoader::start() { @@ -35,47 +34,31 @@ void BatchLoader::start() { } _started = true; - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - for (QUrl url : _urls) { - if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") { - QNetworkRequest request = QNetworkRequest(url); - request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(request); - - qCDebug(scriptengine) << "Downloading file at" << url; - - connect(reply, &QNetworkReply::finished, [=]() { - if (reply->error()) { - _data.insert(url, QString()); - } else { - _data.insert(url, reply->readAll()); - } - reply->deleteLater(); - checkFinished(); - }); - - // If we end up being destroyed before the reply finishes, clean it up - connect(this, &QObject::destroyed, reply, &QObject::deleteLater); - - } else { - QString fileName = url.toLocalFile(); - - // sometimes on windows, we see the toLocalFile() return null, - // in this case we will attempt to simply use the url as a string - if (fileName.isEmpty()) { - fileName = url.toString(); - } - - qCDebug(scriptengine) << "Reading file at " << fileName; - - QFile scriptFile(fileName); - if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { - QTextStream in(&scriptFile); - _data.insert(url, in.readAll()); + + for (const auto& url : _urls) { + auto request = ResourceManager::createResourceRequest(this, url); + if (!request) { + _data.insert(url, QString()); + qCDebug(scriptengine) << "Could not load" << url; + continue; + } + connect(request, &ResourceRequest::finished, this, [=]() { + if (request->getResult() == ResourceRequest::Success) { + _data.insert(url, request->getData()); } else { _data.insert(url, QString()); + qCDebug(scriptengine) << "Could not load" << url; } - } + request->deleteLater(); + checkFinished(); + }); + + // If we end up being destroyed before the reply finishes, clean it up + connect(this, &QObject::destroyed, request, &QObject::deleteLater); + + qCDebug(scriptengine) << "Loading script at " << url; + + request->send(); } checkFinished(); } diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 2047442ce6..9e04cd4ec3 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include "ScriptCache.h" @@ -28,8 +27,6 @@ ScriptCache::ScriptCache(QObject* parent) { } QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool reload) { - assert(!_scriptCache.contains(url) || !reload); - QString scriptContents; if (_scriptCache.contains(url) && !reload) { qCDebug(scriptengine) << "Found script in cache:" << url.toString(); @@ -44,18 +41,10 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is if (alreadyWaiting) { qCDebug(scriptengine) << "Already downloading script at:" << url.toString(); } else { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest = QNetworkRequest(url); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - if (reload) { - networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); - qCDebug(scriptengine) << "Redownloading script at:" << url.toString(); - } else { - qCDebug(scriptengine) << "Downloading script at:" << url.toString(); - } - - QNetworkReply* reply = networkAccessManager.get(networkRequest); - connect(reply, &QNetworkReply::finished, this, &ScriptCache::scriptDownloaded); + auto request = ResourceManager::createResourceRequest(this, url); + request->setCacheEnabled(!reload); + connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded); + request->send(); } } return scriptContents; @@ -69,27 +58,25 @@ void ScriptCache::deleteScript(const QUrl& url) { } void ScriptCache::scriptDownloaded() { - QNetworkReply* reply = qobject_cast(sender()); - QUrl url = reply->url(); + ResourceRequest* req = qobject_cast(sender()); + QUrl url = req->getUrl(); QList scriptUsers = _scriptUsers.values(url); _scriptUsers.remove(url); - if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { - _scriptCache[url] = reply->readAll(); + if (req->getResult() == ResourceRequest::Success) { + _scriptCache[url] = req->getData(); qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); foreach(ScriptUser* user, scriptUsers) { user->scriptContentsAvailable(url, _scriptCache[url]); } } else { - qCWarning(scriptengine) << "Error loading script from URL " << reply->url().toString() - << "- HTTP status code is" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() - << "and error from QNetworkReply is" << reply->errorString(); + qCWarning(scriptengine) << "Error loading script from URL " << url; foreach(ScriptUser* user, scriptUsers) { user->errorInLoadingScript(url); } } - reply->deleteLater(); + req->deleteLater(); } diff --git a/libraries/shared/src/GenericThread.cpp b/libraries/shared/src/GenericThread.cpp index be984d5899..18f5224229 100644 --- a/libraries/shared/src/GenericThread.cpp +++ b/libraries/shared/src/GenericThread.cpp @@ -19,6 +19,7 @@ GenericThread::GenericThread(QObject* parent) : _stopThread(false), _isThreaded(false) // assume non-threaded, must call initialize() { + } GenericThread::~GenericThread() { @@ -32,7 +33,7 @@ void GenericThread::initialize(bool isThreaded, QThread::Priority priority) { _isThreaded = isThreaded; if (_isThreaded) { _thread = new QThread(this); - + // match the thread name to our object name _thread->setObjectName(objectName()); diff --git a/libraries/networking/src/UUIDHasher.h b/libraries/shared/src/UUIDHasher.h similarity index 99% rename from libraries/networking/src/UUIDHasher.h rename to libraries/shared/src/UUIDHasher.h index 0bdfe9792a..8405c6a015 100644 --- a/libraries/networking/src/UUIDHasher.h +++ b/libraries/shared/src/UUIDHasher.h @@ -32,5 +32,4 @@ namespace std { }; } - #endif // hifi_UUIDHasher_h diff --git a/tests/gpu-test/CMakeLists.txt b/tests/gpu-test/CMakeLists.txt index abdbfc07f9..3d42364132 100644 --- a/tests/gpu-test/CMakeLists.txt +++ b/tests/gpu-test/CMakeLists.txt @@ -10,6 +10,6 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") #include_oglplus() # link in the shared libraries -link_hifi_libraries(render-utils gpu shared networking fbx model animation script-engine) +link_hifi_libraries(networking gpu gpu-networking procedural shared fbx model animation script-engine render-utils ) copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/networking/src/PacketTests.cpp b/tests/networking/src/PacketTests.cpp index 8ac4a6d94e..cbb949aa84 100644 --- a/tests/networking/src/PacketTests.cpp +++ b/tests/networking/src/PacketTests.cpp @@ -18,7 +18,7 @@ QTEST_MAIN(PacketTests) std::unique_ptr copyToReadPacket(std::unique_ptr& packet) { auto size = packet->getDataSize(); - auto data = std::unique_ptr(new char[size]); + auto data = std::unique_ptr(new char[size]); memcpy(data.get(), packet->getData(), size); return Packet::fromReceivedPacket(std::move(data), size, HifiSockAddr()); } diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 4878a8867e..6611e8f343 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a8564d7f1c..2056044a4b 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -5,5 +5,8 @@ set_target_properties(mtc PROPERTIES FOLDER "Tools") add_subdirectory(scribe) set_target_properties(scribe PROPERTIES FOLDER "Tools") +add_subdirectory(udt-test) +set_target_properties(udt-test PROPERTIES FOLDER "Tools") + add_subdirectory(vhacd-util) set_target_properties(vhacd-util PROPERTIES FOLDER "Tools") diff --git a/tools/udt-test/CMakeLists.txt b/tools/udt-test/CMakeLists.txt new file mode 100644 index 0000000000..648ef6f00c --- /dev/null +++ b/tools/udt-test/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET_NAME udt-test) +setup_hifi_project() + +link_hifi_libraries(networking shared) + +copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp new file mode 100644 index 0000000000..9a828ebafb --- /dev/null +++ b/tools/udt-test/src/UDTTest.cpp @@ -0,0 +1,410 @@ +// +// UDTTest.cpp +// tools/udt-test/src +// +// Created by Stephen Birarda on 2015-07-30. +// 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 "UDTTest.h" + +#include + +#include +#include +#include + +#include + +const QCommandLineOption PORT_OPTION { "p", "listening port for socket (defaults to random)", "port", 0 }; +const QCommandLineOption TARGET_OPTION { + "target", "target for sent packets (default is listen only)", + "IP:PORT or HOSTNAME:PORT" +}; +const QCommandLineOption PACKET_SIZE { + "packet-size", "size for sent packets in bytes (defaults to 1500)", "bytes", + QString(udt::MAX_PACKET_SIZE_WITH_UDP_HEADER) +}; +const QCommandLineOption MIN_PACKET_SIZE { + "min-packet-size", "min size for sent packets in bytes", "min bytes" +}; +const QCommandLineOption MAX_PACKET_SIZE { + "max-packet-size", "max size for sent packets in bytes", "max bytes" +}; +const QCommandLineOption MAX_SEND_BYTES { + "max-send-bytes", "number of bytes to send before stopping (default is infinite)", "max bytes" +}; +const QCommandLineOption MAX_SEND_PACKETS { + "max-send-packets", "number of packets to send before stopping (default is infinite)", "max packets" +}; +const QCommandLineOption UNRELIABLE_PACKETS { + "unreliable", "send unreliable packets (default is reliable)" +}; +const QCommandLineOption ORDERED_PACKETS { + "ordered", "send ordered packets (default is unordered)" +}; +const QCommandLineOption MESSAGE_SIZE { + "message-size", "megabytes per message payload for ordered sending (default is 20)", "megabytes" +}; +const QCommandLineOption MESSAGE_SEED { + "message-seed", "seed used for random number generation to match ordered messages (default is 742272)", "integer" +}; +const QCommandLineOption STATS_INTERVAL { + "stats-interval", "stats output interval (default is 100ms)", "milliseconds" +}; + +const QStringList CLIENT_STATS_TABLE_HEADERS { + "Send (P/s)", "Est. Max (P/s)", "RTT (ms)", "CW (P)", "Period (us)", + "Recv ACK", "Procd ACK", "Recv LACK", "Recv NAK", "Recv TNAK", + "Sent ACK2", "Sent Packets", "Re-sent Packets" +}; + +const QStringList SERVER_STATS_TABLE_HEADERS { + " Mb/s ", "Recv P/s", "Est. Max (P/s)", "RTT (ms)", "CW (P)", + "Sent ACK", "Sent LACK", "Sent NAK", "Sent TNAK", + "Recv ACK2", "Duplicates (P)" +}; + +UDTTest::UDTTest(int& argc, char** argv) : + QCoreApplication(argc, argv) +{ + qInstallMessageHandler(LogHandler::verboseMessageHandler); + + parseArguments(); + + // randomize the seed for packet size randomization + srand(time(NULL)); + + _socket.bind(QHostAddress::AnyIPv4, _argumentParser.value(PORT_OPTION).toUInt()); + qDebug() << "Test socket is listening on" << _socket.localPort(); + + if (_argumentParser.isSet(TARGET_OPTION)) { + // parse the IP and port combination for this target + QString hostnamePortString = _argumentParser.value(TARGET_OPTION); + + QHostAddress address { hostnamePortString.left(hostnamePortString.indexOf(':')) }; + quint16 port { (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt() }; + + if (address.isNull() || port == 0) { + 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 { + _target = HifiSockAddr(address, port); + qDebug() << "Packets will be sent to" << _target; + } + } + + if (_argumentParser.isSet(PACKET_SIZE)) { + // parse the desired packet size + _minPacketSize = _maxPacketSize = _argumentParser.value(PACKET_SIZE).toInt(); + + if (_argumentParser.isSet(MIN_PACKET_SIZE) || _argumentParser.isSet(MAX_PACKET_SIZE)) { + qCritical() << "Cannot set a min packet size or max packet size AND a specific packet size."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + } + } else { + + bool customMinSize = false; + + if (_argumentParser.isSet(MIN_PACKET_SIZE)) { + _minPacketSize = _argumentParser.value(MIN_PACKET_SIZE).toInt(); + customMinSize = true; + } + + if (_argumentParser.isSet(MAX_PACKET_SIZE)) { + _maxPacketSize = _argumentParser.value(MAX_PACKET_SIZE).toInt(); + + // if we don't have a min packet size we should make it 1, because we have a max + if (customMinSize) { + _minPacketSize = 1; + } + } + + if (_maxPacketSize < _minPacketSize) { + qCritical() << "Cannot set a max packet size that is smaller than the min packet size."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + } + } + + if (_argumentParser.isSet(MAX_SEND_BYTES)) { + _maxSendBytes = _argumentParser.value(MAX_SEND_BYTES).toInt(); + } + + if (_argumentParser.isSet(MAX_SEND_PACKETS)) { + _maxSendPackets = _argumentParser.value(MAX_SEND_PACKETS).toInt(); + } + + if (_argumentParser.isSet(UNRELIABLE_PACKETS)) { + _sendReliable = false; + } + + if (_argumentParser.isSet(ORDERED_PACKETS)) { + _sendOrdered = true; + } + + if (_argumentParser.isSet(MESSAGE_SIZE)) { + if (_argumentParser.isSet(ORDERED_PACKETS)) { + static const double BYTES_PER_MEGABYTE = 1000000; + _messageSize = (int) _argumentParser.value(MESSAGE_SIZE).toInt() * BYTES_PER_MEGABYTE; + + qDebug() << "Message size for ordered packet sending is" << QString("%1MB").arg(_messageSize / BYTES_PER_MEGABYTE); + } else { + qWarning() << "message-size has no effect if not sending ordered - it will be ignored"; + } + } + + + // in case we're an ordered sender or receiver setup our random number generator now + static const int FIRST_MESSAGE_SEED = 742272; + + int messageSeed = FIRST_MESSAGE_SEED; + + if (_argumentParser.isSet(MESSAGE_SEED)) { + messageSeed = _argumentParser.value(MESSAGE_SEED).toInt(); + } + + // seed the generator with a value that the receiver will also use when verifying the ordered message + _generator.seed(messageSeed); + + if (!_target.isNull()) { + sendInitialPackets(); + } else { + // this is a receiver - in case there are ordered packets (messages) being sent to us make sure that we handle them + // so that they can be verified + _socket.setPacketListHandler( + [this](std::unique_ptr packetList) { handlePacketList(std::move(packetList)); }); + } + + // the sender reports stats every 100 milliseconds, unless passed a custom value + + if (_argumentParser.isSet(STATS_INTERVAL)) { + _statsInterval = _argumentParser.value(STATS_INTERVAL).toInt(); + } + + QTimer* statsTimer = new QTimer(this); + connect(statsTimer, &QTimer::timeout, this, &UDTTest::sampleStats); + statsTimer->start(_statsInterval); +} + +void UDTTest::parseArguments() { + // use a QCommandLineParser to setup command line arguments and give helpful output + _argumentParser.setApplicationDescription("High Fidelity UDT Protocol Test Client"); + _argumentParser.addHelpOption(); + + const QCommandLineOption helpOption = _argumentParser.addHelpOption(); + + _argumentParser.addOptions({ + PORT_OPTION, TARGET_OPTION, PACKET_SIZE, MIN_PACKET_SIZE, MAX_PACKET_SIZE, + MAX_SEND_BYTES, MAX_SEND_PACKETS, UNRELIABLE_PACKETS, ORDERED_PACKETS, + MESSAGE_SIZE, MESSAGE_SEED, STATS_INTERVAL + }); + + if (!_argumentParser.parse(arguments())) { + qCritical() << _argumentParser.errorText(); + _argumentParser.showHelp(); + Q_UNREACHABLE(); + } + + if (_argumentParser.isSet(helpOption)) { + _argumentParser.showHelp(); + Q_UNREACHABLE(); + } +} + +void UDTTest::sendInitialPackets() { + static const int NUM_INITIAL_PACKETS = 500; + + int numPackets = std::max(NUM_INITIAL_PACKETS, _maxSendPackets); + + for (int i = 0; i < numPackets; ++i) { + sendPacket(); + } + + if (numPackets == NUM_INITIAL_PACKETS) { + // we've put 500 initial packets in the queue, everytime we hear one has gone out we should add a new one + _socket.connectToSendSignal(_target, this, SLOT(refillPacket())); + } +} + +void UDTTest::sendPacket() { + + if (_maxSendPackets != -1 && _totalQueuedPackets > _maxSendPackets) { + // don't send more packets, we've hit max + return; + } + + if (_maxSendBytes != -1 && _totalQueuedBytes > _maxSendBytes) { + // don't send more packets, we've hit max + return; + } + + // we're good to send a new packet, construct it now + + // figure out what size the packet will be + int packetPayloadSize = 0; + + if (_minPacketSize == _maxPacketSize) { + // we know what size we want - figure out the payload size + packetPayloadSize = _maxPacketSize - udt::Packet::localHeaderSize(false); + } else { + // pick a random size in our range + int randomPacketSize = rand() % _maxPacketSize + _minPacketSize; + packetPayloadSize = randomPacketSize - udt::Packet::localHeaderSize(false); + } + + if (_sendOrdered) { + // check if it is time to add another message - we do this every time 95% of the message size has been sent + static int call = 0; + static int packetSize = udt::Packet::maxPayloadSize(true); + static int messageSizePackets = (int) ceil(_messageSize / udt::Packet::maxPayloadSize(true)); + + static int refillCount = (int) (messageSizePackets * 0.95); + + if (call++ % refillCount == 0) { + // construct a reliable and ordered packet list + auto packetList = std::unique_ptr({ + new udt::PacketList(PacketType::BulkAvatarData, QByteArray(), true, true) + }); + + // fill the packet list with random data according to the constant seed (so receiver can verify) + for (int i = 0; i < messageSizePackets; ++i) { + // setup a QByteArray full of zeros for our random padded data + QByteArray randomPaddedData { packetSize, 0 }; + + // generate a random integer for the first 8 bytes of the random data + uint64_t randomInt = _distribution(_generator); + randomPaddedData.replace(0, sizeof(randomInt), reinterpret_cast(&randomInt), sizeof(randomInt)); + + // write this data to the PacketList + packetList->write(randomPaddedData); + } + + packetList->closeCurrentPacket(false); + + _totalQueuedBytes += packetList->getDataSize(); + _totalQueuedPackets += packetList->getNumPackets(); + + _socket.writePacketList(std::move(packetList), _target); + } + + } else { + auto newPacket = udt::Packet::create(packetPayloadSize, _sendReliable); + newPacket->setPayloadSize(packetPayloadSize); + + _totalQueuedBytes += newPacket->getDataSize(); + + // queue or send this packet by calling write packet on the socket for our target + if (_sendReliable) { + _socket.writePacket(std::move(newPacket), _target); + } else { + _socket.writePacket(*newPacket, _target); + } + + ++_totalQueuedPackets; + } + +} + +void UDTTest::handlePacketList(std::unique_ptr packetList) { + // generate the byte array that should match this message - using the same seed the sender did + + int packetSize = udt::Packet::maxPayloadSize(true); + int messageSize = packetList->getMessageSize(); + + QByteArray messageData(messageSize, 0); + + for (int i = 0; i < messageSize; i += packetSize) { + // generate the random 64-bit unsigned integer that should lead this packet + uint64_t randomInt = _distribution(_generator); + + messageData.replace(i, sizeof(randomInt), reinterpret_cast(&randomInt), sizeof(randomInt)); + } + + bool dataMatch = messageData == packetList->getMessage(); + + Q_ASSERT_X(dataMatch, "UDTTest::handlePacketList", + "received message did not match expected message (from seeded random number generation)."); + + if (!dataMatch) { + qCritical() << "UDTTest::handlePacketList" << "received message did not match expected message" + << "(from seeded random number generation)."; + } +} + +void UDTTest::sampleStats() { + static bool first = true; + static const double USECS_PER_MSEC = 1000.0; + + if (!_target.isNull()) { + if (first) { + // output the headers for stats for our table + qDebug() << qPrintable(CLIENT_STATS_TABLE_HEADERS.join(" | ")); + first = false; + } + + udt::ConnectionStats::Stats stats = _socket.sampleStatsForConnection(_target); + + int headerIndex = -1; + + // setup a list of left justified values + QStringList values { + QString::number(stats.sendRate).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.estimatedBandwith).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.rtt / USECS_PER_MSEC, 'f', 2).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.congestionWindowSize).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.packetSendPeriod).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::ProcessedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedLightACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedNAK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedTimeoutNAK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::SentACK2]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.sentPackets).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::Retransmission]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()) + }; + + // output this line of values + qDebug() << qPrintable(values.join(" | ")); + } else { + if (first) { + // output the headers for stats for our table + qDebug() << qPrintable(SERVER_STATS_TABLE_HEADERS.join(" | ")); + first = false; + } + + auto sockets = _socket.getConnectionSockAddrs(); + if (sockets.size() > 0) { + udt::ConnectionStats::Stats stats = _socket.sampleStatsForConnection(sockets.front()); + + int headerIndex = -1; + + static const double MEGABITS_PER_BYTE = 8.0 / 1000000.0; + static const double MS_PER_SECOND = 1000.0; + + double megabitsPerSecond = (stats.receivedBytes * MEGABITS_PER_BYTE * MS_PER_SECOND) / _statsInterval; + + // setup a list of left justified values + QStringList values { + QString::number(megabitsPerSecond, 'f', 2).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.receiveRate).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.estimatedBandwith).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.rtt / USECS_PER_MSEC, 'f', 2).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.congestionWindowSize).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::SentACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::SentLightACK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::SentNAK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::SentTimeoutNAK]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK2]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()), + QString::number(stats.events[udt::ConnectionStats::Stats::Duplicate]).rightJustified(SERVER_STATS_TABLE_HEADERS[++headerIndex].size()) + }; + + // output this line of values + qDebug() << qPrintable(values.join(" | ")); + } + } +} diff --git a/tools/udt-test/src/UDTTest.h b/tools/udt-test/src/UDTTest.h new file mode 100644 index 0000000000..fda57cc183 --- /dev/null +++ b/tools/udt-test/src/UDTTest.h @@ -0,0 +1,67 @@ +// +// UDTTest.h +// tools/udt-test/src +// +// Created by Stephen Birarda on 2015-07-30. +// 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 +// + +#pragma once + +#ifndef hifi_UDTTest_h +#define hifi_UDTTest_h + + +#include + +#include +#include + +#include +#include + +class UDTTest : public QCoreApplication { + Q_OBJECT +public: + UDTTest(int& argc, char** argv); + +public slots: + void refillPacket() { sendPacket(); } // adds a new packet to the queue when we are told one is sent + void sampleStats(); + +private: + void parseArguments(); + void handlePacketList(std::unique_ptr packetList); + + void sendInitialPackets(); // fills the queue with packets to start + void sendPacket(); // constructs and sends a packet according to the test parameters + + QCommandLineParser _argumentParser; + udt::Socket _socket; + + HifiSockAddr _target; // the target for sent packets + + int _minPacketSize { udt::MAX_PACKET_SIZE }; + int _maxPacketSize { udt::MAX_PACKET_SIZE }; + int _maxSendBytes { -1 }; // the number of bytes to send to the target before stopping + int _maxSendPackets { -1 }; // the number of packets to send to the target before stopping + + bool _sendReliable { true }; // whether packets are sent reliably or unreliably + bool _sendOrdered { false }; // whether to send ordered packets + + int _messageSize { 10000000 }; // number of bytes per message while sending ordered + + std::random_device _randomDevice; + std::mt19937 _generator { _randomDevice() }; // random number generator for ordered data testing + std::uniform_int_distribution _distribution { 1, UINT64_MAX }; // producer of random integer values + + int _totalQueuedPackets { 0 }; // keeps track of the number of packets we have already queued + int _totalQueuedBytes { 0 }; // keeps track of the number of bytes we have already queued + + int _statsInterval { 100 }; // recording interval for stats in milliseconds +}; + +#endif // hifi_UDTTest_h diff --git a/tools/udt-test/src/main.cpp b/tools/udt-test/src/main.cpp new file mode 100644 index 0000000000..ccb7d0af0f --- /dev/null +++ b/tools/udt-test/src/main.cpp @@ -0,0 +1,19 @@ +// +// main.cpp +// tools/udt-test/src +// +// Created by Stephen Birarda on 7/30/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 + +#include "UDTTest.h" + +int main(int argc, char* argv[]) { + UDTTest app(argc, argv); + return app.exec(); +} +