//// 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 <QtCore/QCoreApplication>#include <QtCore/QCryptographicHash>#include <QtCore/QDateTime>#include <QtCore/QDir>#include <QtCore/QFile>#include <QtCore/QFileInfo>#include <QtCore/QJsonDocument>#include <QtCore/QString> #include <ServerPathUtils.h> #include "NetworkLogging.h"#include "NodeType.h"#include "SendAssetTask.h"#include "UploadAssetTask.h" const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; AssetServer::AssetServer(ReceivedMessage& message) : ThreadedAssignment(message), _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<NodeList>()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");} void AssetServer::run() { qDebug() << "Waiting for connection to domain to request settings from domain-server."; // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &AssetServer::completeSetup); connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AssetServer::domainSettingsRequestFailed); ThreadedAssignment::commonInit(ASSET_SERVER_LOGGING_TARGET_NAME, NodeType::AssetServer);} static const QString ASSET_FILES_SUBDIR = "files"; void AssetServer::completeSetup() { auto nodeList = DependencyManager::get<NodeList>(); auto& domainHandler = nodeList->getDomainHandler(); const QJsonObject& settingsObject = domainHandler.getSettingsObject(); static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server"; if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) { qCritical() << "Received settings from the domain-server with no asset-server section. Stopping assignment."; setFinished(true); return; } auto assetServerObject = settingsObject[ASSET_SERVER_SETTINGS_KEY].toObject(); static const QString MAX_BANDWIDTH_OPTION = "max_bandwidth"; auto maxBandwidthValue = assetServerObject[MAX_BANDWIDTH_OPTION]; auto maxBandwidthFloat = maxBandwidthValue.toDouble(-1); if (maxBandwidthFloat > 0.0) { const int BITS_PER_MEGABITS = 1000 * 1000; int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS; nodeList->setConnectionMaxBandwidth(maxBandwidth); qInfo() << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s." " (" << maxBandwidth << "bits/s)"; } // get the path to the asset folder from the domain server settings static const QString ASSETS_PATH_OPTION = "assets_path"; auto assetsJSONValue = assetServerObject[ASSETS_PATH_OPTION]; if (!assetsJSONValue.isString()) { qCritical() << "Received an assets path from the domain-server that could not be parsed. Stopping assignment."; setFinished(true); return; } auto assetsPathString = assetsJSONValue.toString(); QDir assetsPath { assetsPathString }; QString absoluteFilePath = assetsPath.absolutePath(); if (assetsPath.isRelative()) { // if the domain settings passed us a relative path, make an absolute path that is relative to the // default data directory absoluteFilePath = ServerPathUtils::getDataFilePath("assets/" + assetsPathString); } _resourcesDirectory = QDir(absoluteFilePath); qDebug() << "Creating resources directory"; _resourcesDirectory.mkpath("."); _filesDirectory = _resourcesDirectory; if (!_resourcesDirectory.mkpath(ASSET_FILES_SUBDIR) || !_filesDirectory.cd(ASSET_FILES_SUBDIR)) { qCritical() << "Unable to create file directory for asset-server files. Stopping assignment."; setFinished(true); return; } // load whatever mappings we currently have from the local file if (loadMappingsFromFile()) { qInfo() << "Serving files from: " << _filesDirectory.path(); // Check the asset directory to output some information about what we have auto files = _filesDirectory.entryList(QDir::Files); QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING }; auto hashedFiles = files.filter(hashFileRegex); qInfo() << "There are" << hashedFiles.size() << "asset files in the asset directory."; if (_fileMappings.count() > 0) { cleanupUnmappedFiles(); } nodeList->addNodeTypeToInterestSet(NodeType::Agent); } else { qCritical() << "Asset Server assignment will not continue because mapping file could not be loaded."; setFinished(true); } } void AssetServer::cleanupUnmappedFiles() { QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}" }; auto files = _filesDirectory.entryInfoList(QDir::Files); // grab the currently mapped hashes auto mappedHashes = _fileMappings.values(); qInfo() << "Performing unmapped asset cleanup."; for (const auto& fileInfo : files) { if (hashFileRegex.exactMatch(fileInfo.fileName())) { if (!mappedHashes.contains(fileInfo.fileName())) { // remove the unmapped file QFile removeableFile { fileInfo.absoluteFilePath() }; if (removeableFile.remove()) { qDebug() << "\tDeleted" << fileInfo.fileName() << "from asset files directory since it is unmapped."; } else { qDebug() << "\tAttempt to delete unmapped file" << fileInfo.fileName() << "failed"; } } } }} void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { MessageID messageID; message->readPrimitive(&messageID); AssetMappingOperationType operationType; message->readPrimitive(&operationType); auto replyPacket = NLPacketList::create(PacketType::AssetMappingOperationReply, QByteArray(), true, true); replyPacket->writePrimitive(messageID); switch (operationType) { case AssetMappingOperationType::Get: { handleGetMappingOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::GetAll: { handleGetAllMappingOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::Set: { handleSetMappingOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::Delete: { handleDeleteMappingsOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::Rename: { handleRenameMappingOperation(*message, senderNode, *replyPacket); break; } } auto nodeList = DependencyManager::get<NodeList>(); nodeList->sendPacketList(std::move(replyPacket), *senderNode);} void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { QString assetPath = message.readString(); auto it = _fileMappings.find(assetPath); if (it != _fileMappings.end()) { auto assetHash = it->toString(); replyPacket.writePrimitive(AssetServerError::NoError); replyPacket.write(QByteArray::fromHex(assetHash.toUtf8())); } else { replyPacket.writePrimitive(AssetServerError::AssetNotFound); }} void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { replyPacket.writePrimitive(AssetServerError::NoError); auto count = _fileMappings.size(); replyPacket.writePrimitive(count); for (auto it = _fileMappings.cbegin(); it != _fileMappings.cend(); ++ it) { replyPacket.writeString(it.key()); replyPacket.write(QByteArray::fromHex(it.value().toString().toUtf8())); }} void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { if (senderNode->getCanWriteToAssetServer()) { QString assetPath = message.readString(); auto assetHash = message.read(SHA256_HASH_LENGTH).toHex(); if (setMapping(assetPath, assetHash)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); } } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); }} void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { if (senderNode->getCanWriteToAssetServer()) { int numberOfDeletedMappings { 0 }; message.readPrimitive(&numberOfDeletedMappings); QStringList mappingsToDelete; for (int i = 0; i < numberOfDeletedMappings; ++i) { mappingsToDelete << message.readString(); } if (deleteMappings(mappingsToDelete)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); } } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); }} void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { if (senderNode->getCanWriteToAssetServer()) { QString oldPath = message.readString(); QString newPath = message.readString(); if (renameMapping(oldPath, newPath)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); } } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); }} void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { QByteArray assetHash; MessageID messageID; if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) { qDebug() << "ERROR bad file request"; return; } message->readPrimitive(&messageID); assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH); auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply); QByteArray hexHash = assetHash.toHex(); replyPacket->writePrimitive(messageID); replyPacket->write(assetHash); QString fileName = QString(hexHash); QFileInfo fileInfo { _filesDirectory.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>(); nodeList->sendPacket(std::move(replyPacket), *senderNode);} void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset)); if (message->getSize() < minSize) { qDebug() << "ERROR bad file request"; return; } // Queue task auto task = new SendAssetTask(message, senderNode, _filesDirectory); _taskPool.start(task);} void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { if (senderNode->getCanWriteToAssetServer()) { qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); auto task = new UploadAssetTask(message, senderNode, _filesDirectory); _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; message->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>(); nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode); }} void AssetServer::sendStatsPacket() { QJsonObject serverStats; auto stats = DependencyManager::get<NodeList>()->sampleStatsForAllConnections(); for (const auto& stat : stats) { QJsonObject nodeStats; auto endTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stat.second.endTime); QDateTime date = QDateTime::fromMSecsSinceEpoch(endTimeMs.count()); static const float USEC_PER_SEC = 1000000.0f; static const float MEGABITS_PER_BYTE = 8.0f / 1000000.0f; // Bytes => Mbits float elapsed = (float)(stat.second.endTime - stat.second.startTime).count() / USEC_PER_SEC; // sec float megabitsPerSecPerByte = MEGABITS_PER_BYTE / elapsed; // Bytes => Mb/s QJsonObject connectionStats; connectionStats["1. Last Heard"] = date.toString(); connectionStats["2. Est. Max (P/s)"] = stat.second.estimatedBandwith; connectionStats["3. RTT (ms)"] = stat.second.rtt; connectionStats["4. CW (P)"] = stat.second.congestionWindowSize; connectionStats["5. Period (us)"] = stat.second.packetSendPeriod; connectionStats["6. Up (Mb/s)"] = stat.second.sentBytes * megabitsPerSecPerByte; connectionStats["7. Down (Mb/s)"] = stat.second.receivedBytes * megabitsPerSecPerByte; nodeStats["Connection Stats"] = connectionStats; using Events = udt::ConnectionStats::Stats::Event; const auto& events = stat.second.events; QJsonObject upstreamStats; upstreamStats["1. Sent (P/s)"] = stat.second.sendRate; upstreamStats["2. Sent Packets"] = stat.second.sentPackets; upstreamStats["3. Recvd ACK"] = events[Events::ReceivedACK]; upstreamStats["4. Procd ACK"] = events[Events::ProcessedACK]; upstreamStats["5. Recvd LACK"] = events[Events::ReceivedLightACK]; upstreamStats["6. Recvd NAK"] = events[Events::ReceivedNAK]; upstreamStats["7. Recvd TNAK"] = events[Events::ReceivedTimeoutNAK]; upstreamStats["8. Sent ACK2"] = events[Events::SentACK2]; upstreamStats["9. Retransmitted"] = events[Events::Retransmission]; nodeStats["Upstream Stats"] = upstreamStats; QJsonObject downstreamStats; downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate; downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets; downstreamStats["3. Sent ACK"] = events[Events::SentACK]; downstreamStats["4. Sent LACK"] = events[Events::SentLightACK]; downstreamStats["5. Sent NAK"] = events[Events::SentNAK]; downstreamStats["6. Sent TNAK"] = events[Events::SentTimeoutNAK]; downstreamStats["7. Recvd ACK2"] = events[Events::ReceivedACK2]; downstreamStats["8. Duplicates"] = events[Events::Duplicate]; nodeStats["Downstream Stats"] = downstreamStats; QString uuid; auto nodelist = DependencyManager::get<NodeList>(); if (stat.first == nodelist->getDomainHandler().getSockAddr()) { uuid = uuidStringWithoutCurlyBraces(nodelist->getDomainHandler().getUUID()); nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = "DomainServer"; } else { auto node = nodelist->findNodeWithAddr(stat.first); uuid = uuidStringWithoutCurlyBraces(node ? node->getUUID() : QUuid()); nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuid; } serverStats[uuid] = nodeStats; } // send off the stats packets ThreadedAssignment::addPacketStatsAndSendStatsPacket(serverStats);} static const QString MAP_FILE_NAME = "map.json"; bool AssetServer::loadMappingsFromFile() { auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME); QFile mapFile { mapFilePath }; if (mapFile.exists()) { if (mapFile.open(QIODevice::ReadOnly)) { QJsonParseError error; auto jsonDocument = QJsonDocument::fromJson(mapFile.readAll(), &error); if (error.error == QJsonParseError::NoError) { _fileMappings = jsonDocument.object().toVariantHash(); // remove any mappings that don't match the expected format auto it = _fileMappings.begin(); while (it != _fileMappings.end()) { bool shouldDrop = false; if (!isValidPath(it.key())) { qWarning() << "Will not keep mapping for" << it.key() << "since it is not a valid path."; shouldDrop = true; } if (!isValidHash(it.value().toString())) { qWarning() << "Will not keep mapping for" << it.key() << "since it does not have a valid hash."; shouldDrop = true; } if (shouldDrop) { it = _fileMappings.erase(it); } else { ++it; } } qInfo() << "Loaded" << _fileMappings.count() << "mappings from map file at" << mapFilePath; return true; } } qCritical() << "Failed to read mapping file at" << mapFilePath; return false; } else { qInfo() << "No existing mappings loaded from file since no file was found at" << mapFilePath; } return true;} bool AssetServer::writeMappingsToFile() { auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME); QFile mapFile { mapFilePath }; if (mapFile.open(QIODevice::WriteOnly)) { auto jsonObject = QJsonObject::fromVariantHash(_fileMappings); QJsonDocument jsonDocument { jsonObject }; if (mapFile.write(jsonDocument.toJson()) != -1) { qDebug() << "Wrote JSON mappings to file at" << mapFilePath; return true; } else { qWarning() << "Failed to write JSON mappings to file at" << mapFilePath; } } else {Lines 1–500 of 730. Load more
// // 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 <QtCore/QCoreApplication> #include <QtCore/QCryptographicHash> #include <QtCore/QDateTime> #include <QtCore/QDir> #include <QtCore/QFile> #include <QtCore/QFileInfo> #include <QtCore/QJsonDocument> #include <QtCore/QString> #include <ServerPathUtils.h> #include "NetworkLogging.h" #include "NodeType.h" #include "SendAssetTask.h" #include "UploadAssetTask.h" const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; AssetServer::AssetServer(ReceivedMessage& message) : ThreadedAssignment(message), _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<NodeList>()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation"); } void AssetServer::run() { qDebug() << "Waiting for connection to domain to request settings from domain-server."; // wait until we have the domain-server settings, otherwise we bail DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &AssetServer::completeSetup); connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AssetServer::domainSettingsRequestFailed); ThreadedAssignment::commonInit(ASSET_SERVER_LOGGING_TARGET_NAME, NodeType::AssetServer); } static const QString ASSET_FILES_SUBDIR = "files"; void AssetServer::completeSetup() { auto nodeList = DependencyManager::get<NodeList>(); auto& domainHandler = nodeList->getDomainHandler(); const QJsonObject& settingsObject = domainHandler.getSettingsObject(); static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server"; if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) { qCritical() << "Received settings from the domain-server with no asset-server section. Stopping assignment."; setFinished(true); return; } auto assetServerObject = settingsObject[ASSET_SERVER_SETTINGS_KEY].toObject(); static const QString MAX_BANDWIDTH_OPTION = "max_bandwidth"; auto maxBandwidthValue = assetServerObject[MAX_BANDWIDTH_OPTION]; auto maxBandwidthFloat = maxBandwidthValue.toDouble(-1); if (maxBandwidthFloat > 0.0) { const int BITS_PER_MEGABITS = 1000 * 1000; int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS; nodeList->setConnectionMaxBandwidth(maxBandwidth); qInfo() << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s." " (" << maxBandwidth << "bits/s)"; } // get the path to the asset folder from the domain server settings static const QString ASSETS_PATH_OPTION = "assets_path"; auto assetsJSONValue = assetServerObject[ASSETS_PATH_OPTION]; if (!assetsJSONValue.isString()) { qCritical() << "Received an assets path from the domain-server that could not be parsed. Stopping assignment."; setFinished(true); return; } auto assetsPathString = assetsJSONValue.toString(); QDir assetsPath { assetsPathString }; QString absoluteFilePath = assetsPath.absolutePath(); if (assetsPath.isRelative()) { // if the domain settings passed us a relative path, make an absolute path that is relative to the // default data directory absoluteFilePath = ServerPathUtils::getDataFilePath("assets/" + assetsPathString); } _resourcesDirectory = QDir(absoluteFilePath); qDebug() << "Creating resources directory"; _resourcesDirectory.mkpath("."); _filesDirectory = _resourcesDirectory; if (!_resourcesDirectory.mkpath(ASSET_FILES_SUBDIR) || !_filesDirectory.cd(ASSET_FILES_SUBDIR)) { qCritical() << "Unable to create file directory for asset-server files. Stopping assignment."; setFinished(true); return; } // load whatever mappings we currently have from the local file if (loadMappingsFromFile()) { qInfo() << "Serving files from: " << _filesDirectory.path(); // Check the asset directory to output some information about what we have auto files = _filesDirectory.entryList(QDir::Files); QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING }; auto hashedFiles = files.filter(hashFileRegex); qInfo() << "There are" << hashedFiles.size() << "asset files in the asset directory."; if (_fileMappings.count() > 0) { cleanupUnmappedFiles(); } nodeList->addNodeTypeToInterestSet(NodeType::Agent); } else { qCritical() << "Asset Server assignment will not continue because mapping file could not be loaded."; setFinished(true); } } void AssetServer::cleanupUnmappedFiles() { QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}" }; auto files = _filesDirectory.entryInfoList(QDir::Files); // grab the currently mapped hashes auto mappedHashes = _fileMappings.values(); qInfo() << "Performing unmapped asset cleanup."; for (const auto& fileInfo : files) { if (hashFileRegex.exactMatch(fileInfo.fileName())) { if (!mappedHashes.contains(fileInfo.fileName())) { // remove the unmapped file QFile removeableFile { fileInfo.absoluteFilePath() }; if (removeableFile.remove()) { qDebug() << "\tDeleted" << fileInfo.fileName() << "from asset files directory since it is unmapped."; } else { qDebug() << "\tAttempt to delete unmapped file" << fileInfo.fileName() << "failed"; } } } } } void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { MessageID messageID; message->readPrimitive(&messageID); AssetMappingOperationType operationType; message->readPrimitive(&operationType); auto replyPacket = NLPacketList::create(PacketType::AssetMappingOperationReply, QByteArray(), true, true); replyPacket->writePrimitive(messageID); switch (operationType) { case AssetMappingOperationType::Get: { handleGetMappingOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::GetAll: { handleGetAllMappingOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::Set: { handleSetMappingOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::Delete: { handleDeleteMappingsOperation(*message, senderNode, *replyPacket); break; } case AssetMappingOperationType::Rename: { handleRenameMappingOperation(*message, senderNode, *replyPacket); break; } } auto nodeList = DependencyManager::get<NodeList>(); nodeList->sendPacketList(std::move(replyPacket), *senderNode); } void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { QString assetPath = message.readString(); auto it = _fileMappings.find(assetPath); if (it != _fileMappings.end()) { auto assetHash = it->toString(); replyPacket.writePrimitive(AssetServerError::NoError); replyPacket.write(QByteArray::fromHex(assetHash.toUtf8())); } else { replyPacket.writePrimitive(AssetServerError::AssetNotFound); } } void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { replyPacket.writePrimitive(AssetServerError::NoError); auto count = _fileMappings.size(); replyPacket.writePrimitive(count); for (auto it = _fileMappings.cbegin(); it != _fileMappings.cend(); ++ it) { replyPacket.writeString(it.key()); replyPacket.write(QByteArray::fromHex(it.value().toString().toUtf8())); } } void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { if (senderNode->getCanWriteToAssetServer()) { QString assetPath = message.readString(); auto assetHash = message.read(SHA256_HASH_LENGTH).toHex(); if (setMapping(assetPath, assetHash)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); } } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); } } void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { if (senderNode->getCanWriteToAssetServer()) { int numberOfDeletedMappings { 0 }; message.readPrimitive(&numberOfDeletedMappings); QStringList mappingsToDelete; for (int i = 0; i < numberOfDeletedMappings; ++i) { mappingsToDelete << message.readString(); } if (deleteMappings(mappingsToDelete)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); } } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); } } void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { if (senderNode->getCanWriteToAssetServer()) { QString oldPath = message.readString(); QString newPath = message.readString(); if (renameMapping(oldPath, newPath)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); } } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); } } void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { QByteArray assetHash; MessageID messageID; if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) { qDebug() << "ERROR bad file request"; return; } message->readPrimitive(&messageID); assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH); auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply); QByteArray hexHash = assetHash.toHex(); replyPacket->writePrimitive(messageID); replyPacket->write(assetHash); QString fileName = QString(hexHash); QFileInfo fileInfo { _filesDirectory.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>(); nodeList->sendPacket(std::move(replyPacket), *senderNode); } void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset)); if (message->getSize() < minSize) { qDebug() << "ERROR bad file request"; return; } // Queue task auto task = new SendAssetTask(message, senderNode, _filesDirectory); _taskPool.start(task); } void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) { if (senderNode->getCanWriteToAssetServer()) { qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); auto task = new UploadAssetTask(message, senderNode, _filesDirectory); _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; message->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>(); nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode); } } void AssetServer::sendStatsPacket() { QJsonObject serverStats; auto stats = DependencyManager::get<NodeList>()->sampleStatsForAllConnections(); for (const auto& stat : stats) { QJsonObject nodeStats; auto endTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stat.second.endTime); QDateTime date = QDateTime::fromMSecsSinceEpoch(endTimeMs.count()); static const float USEC_PER_SEC = 1000000.0f; static const float MEGABITS_PER_BYTE = 8.0f / 1000000.0f; // Bytes => Mbits float elapsed = (float)(stat.second.endTime - stat.second.startTime).count() / USEC_PER_SEC; // sec float megabitsPerSecPerByte = MEGABITS_PER_BYTE / elapsed; // Bytes => Mb/s QJsonObject connectionStats; connectionStats["1. Last Heard"] = date.toString(); connectionStats["2. Est. Max (P/s)"] = stat.second.estimatedBandwith; connectionStats["3. RTT (ms)"] = stat.second.rtt; connectionStats["4. CW (P)"] = stat.second.congestionWindowSize; connectionStats["5. Period (us)"] = stat.second.packetSendPeriod; connectionStats["6. Up (Mb/s)"] = stat.second.sentBytes * megabitsPerSecPerByte; connectionStats["7. Down (Mb/s)"] = stat.second.receivedBytes * megabitsPerSecPerByte; nodeStats["Connection Stats"] = connectionStats; using Events = udt::ConnectionStats::Stats::Event; const auto& events = stat.second.events; QJsonObject upstreamStats; upstreamStats["1. Sent (P/s)"] = stat.second.sendRate; upstreamStats["2. Sent Packets"] = stat.second.sentPackets; upstreamStats["3. Recvd ACK"] = events[Events::ReceivedACK]; upstreamStats["4. Procd ACK"] = events[Events::ProcessedACK]; upstreamStats["5. Recvd LACK"] = events[Events::ReceivedLightACK]; upstreamStats["6. Recvd NAK"] = events[Events::ReceivedNAK]; upstreamStats["7. Recvd TNAK"] = events[Events::ReceivedTimeoutNAK]; upstreamStats["8. Sent ACK2"] = events[Events::SentACK2]; upstreamStats["9. Retransmitted"] = events[Events::Retransmission]; nodeStats["Upstream Stats"] = upstreamStats; QJsonObject downstreamStats; downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate; downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets; downstreamStats["3. Sent ACK"] = events[Events::SentACK]; downstreamStats["4. Sent LACK"] = events[Events::SentLightACK]; downstreamStats["5. Sent NAK"] = events[Events::SentNAK]; downstreamStats["6. Sent TNAK"] = events[Events::SentTimeoutNAK]; downstreamStats["7. Recvd ACK2"] = events[Events::ReceivedACK2]; downstreamStats["8. Duplicates"] = events[Events::Duplicate]; nodeStats["Downstream Stats"] = downstreamStats; QString uuid; auto nodelist = DependencyManager::get<NodeList>(); if (stat.first == nodelist->getDomainHandler().getSockAddr()) { uuid = uuidStringWithoutCurlyBraces(nodelist->getDomainHandler().getUUID()); nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = "DomainServer"; } else { auto node = nodelist->findNodeWithAddr(stat.first); uuid = uuidStringWithoutCurlyBraces(node ? node->getUUID() : QUuid()); nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuid; } serverStats[uuid] = nodeStats; } // send off the stats packets ThreadedAssignment::addPacketStatsAndSendStatsPacket(serverStats); } static const QString MAP_FILE_NAME = "map.json"; bool AssetServer::loadMappingsFromFile() { auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME); QFile mapFile { mapFilePath }; if (mapFile.exists()) { if (mapFile.open(QIODevice::ReadOnly)) { QJsonParseError error; auto jsonDocument = QJsonDocument::fromJson(mapFile.readAll(), &error); if (error.error == QJsonParseError::NoError) { _fileMappings = jsonDocument.object().toVariantHash(); // remove any mappings that don't match the expected format auto it = _fileMappings.begin(); while (it != _fileMappings.end()) { bool shouldDrop = false; if (!isValidPath(it.key())) { qWarning() << "Will not keep mapping for" << it.key() << "since it is not a valid path."; shouldDrop = true; } if (!isValidHash(it.value().toString())) { qWarning() << "Will not keep mapping for" << it.key() << "since it does not have a valid hash."; shouldDrop = true; } if (shouldDrop) { it = _fileMappings.erase(it); } else { ++it; } } qInfo() << "Loaded" << _fileMappings.count() << "mappings from map file at" << mapFilePath; return true; } } qCritical() << "Failed to read mapping file at" << mapFilePath; return false; } else { qInfo() << "No existing mappings loaded from file since no file was found at" << mapFilePath; } return true; } bool AssetServer::writeMappingsToFile() { auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME); QFile mapFile { mapFilePath }; if (mapFile.open(QIODevice::WriteOnly)) { auto jsonObject = QJsonObject::fromVariantHash(_fileMappings); QJsonDocument jsonDocument { jsonObject }; if (mapFile.write(jsonDocument.toJson()) != -1) { qDebug() << "Wrote JSON mappings to file at" << mapFilePath; return true; } else { qWarning() << "Failed to write JSON mappings to file at" << mapFilePath; } } else {