mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-04-10 02:44:34 +02:00
handle whitelist avatar URL override via traits
This commit is contained in:
parent
a80d19a44a
commit
be7eb57205
20 changed files with 224 additions and 151 deletions
|
@ -25,16 +25,19 @@
|
|||
#include <AudioInjectorManager.h>
|
||||
#include <AssetClient.h>
|
||||
#include <DebugDraw.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <LocationScriptingInterface.h>
|
||||
#include <MessagesClient.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <NodeList.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <SoundCacheScriptingInterface.h>
|
||||
#include <SoundCache.h>
|
||||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
#include <UsersScriptingInterface.h>
|
||||
#include <UUID.h>
|
||||
|
||||
|
@ -49,12 +52,12 @@
|
|||
#include <WebSocketServerClass.h>
|
||||
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
|
||||
#include "AssignmentDynamicFactory.h"
|
||||
#include "entities/AssignmentParentFinder.h"
|
||||
#include "RecordingScriptingInterface.h"
|
||||
#include "AbstractAudioInterface.h"
|
||||
#include "AgentScriptingInterface.h"
|
||||
|
||||
|
||||
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
|
||||
|
||||
Agent::Agent(ReceivedMessage& message) :
|
||||
|
@ -63,6 +66,18 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
_audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO),
|
||||
_avatarAudioTimer(this)
|
||||
{
|
||||
DependencyManager::set<ScriptableAvatar>();
|
||||
|
||||
DependencyManager::set<AnimationCache>();
|
||||
DependencyManager::set<AnimationCacheScriptingInterface>();
|
||||
DependencyManager::set<EntityScriptingInterface>(false);
|
||||
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
DependencyManager::set<AssignmentDynamicFactory>();
|
||||
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
||||
|
||||
_entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT);
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
|
@ -99,7 +114,6 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
|
||||
|
||||
// 100Hz timer for audio
|
||||
const int TARGET_INTERVAL_MSEC = 10; // 10ms
|
||||
connect(&_avatarAudioTimer, &QTimer::timeout, this, &Agent::processAgentAvatarAudio);
|
||||
|
@ -439,7 +453,7 @@ void Agent::executeScript() {
|
|||
encodedBuffer = audio;
|
||||
}
|
||||
|
||||
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false,
|
||||
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false,
|
||||
audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0),
|
||||
packetType, _selectedCodecName);
|
||||
});
|
||||
|
@ -842,6 +856,9 @@ void Agent::aboutToFinish() {
|
|||
DependencyManager::destroy<recording::Recorder>();
|
||||
DependencyManager::destroy<recording::ClipCache>();
|
||||
DependencyManager::destroy<ScriptEngine>();
|
||||
|
||||
DependencyManager::destroy<ScriptableAvatar>();
|
||||
|
||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
||||
|
||||
// cleanup codec & encoder
|
||||
|
|
|
@ -21,10 +21,7 @@
|
|||
#include <shared/QtHelpers.h>
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <AnimationCacheScriptingInterface.h>
|
||||
#include <Assignment.h>
|
||||
#include <AvatarHashMap.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <LogHandler.h>
|
||||
#include <LogUtils.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
@ -32,16 +29,12 @@
|
|||
#include <udt/PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <ShutdownEventListener.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
|
||||
#include <Trace.h>
|
||||
#include <StatTracker.h>
|
||||
|
||||
#include "AssignmentClientLogging.h"
|
||||
#include "AssignmentDynamicFactory.h"
|
||||
#include "AssignmentFactory.h"
|
||||
#include "avatars/ScriptableAvatar.h"
|
||||
|
||||
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
|
||||
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
|
||||
|
@ -57,21 +50,11 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
DependencyManager::set<StatTracker>();
|
||||
DependencyManager::set<AccountManager>();
|
||||
|
||||
auto scriptableAvatar = DependencyManager::set<ScriptableAvatar>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
|
||||
// create a NodeList as an unassigned client, must be after addressManager
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned, listenPort);
|
||||
|
||||
auto animationCache = DependencyManager::set<AnimationCache>();
|
||||
DependencyManager::set<AnimationCacheScriptingInterface>();
|
||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>(false);
|
||||
|
||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||
auto dynamicFactory = DependencyManager::set<AssignmentDynamicFactory>();
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
||||
|
||||
nodeList->startThread();
|
||||
// set the logging target to the the CHILD_TARGET_NAME
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
|
||||
|
|
|
@ -39,7 +39,8 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
|||
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
|
||||
|
||||
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message)
|
||||
ThreadedAssignment(message),
|
||||
_slavePool(&_slaveSharedData)
|
||||
{
|
||||
// make sure we hear about node kills so we can tell the other nodes
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
||||
|
@ -338,17 +339,7 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
|||
sendIdentity = true;
|
||||
qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
|
||||
}
|
||||
if (nodeData && nodeData->getAvatarSkeletonModelUrlMustChange()) { // never true for an empty _avatarWhitelist
|
||||
nodeData->setAvatarSkeletonModelUrlMustChange(false);
|
||||
AvatarData& avatar = nodeData->getAvatar();
|
||||
static const QUrl emptyURL("");
|
||||
QUrl url = avatar.cannonicalSkeletonModelURL(emptyURL);
|
||||
if (!isAvatarInWhitelist(url)) {
|
||||
qCDebug(avatars) << "Forbidden avatar" << nodeData->getNodeID() << avatar.getSkeletonModelURL() << "replaced with" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
|
||||
avatar.setSkeletonModelURL(_replacementAvatar);
|
||||
sendIdentity = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (sendIdentity && !node->isUpstream()) {
|
||||
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
|
||||
// since this packet includes a change to either the skeleton model URL or the display name
|
||||
|
@ -360,22 +351,6 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AvatarMixer::isAvatarInWhitelist(const QUrl& url) {
|
||||
// The avatar is in the whitelist if:
|
||||
// 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND
|
||||
// 2. The avatar's URL's path starts with the path of that same URL in the whitelist
|
||||
for (const auto& whiteListedPrefix : _avatarWhitelist) {
|
||||
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
|
||||
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
|
||||
if (url.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
|
||||
url.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
|
||||
// throttle using a modified proportional-integral controller
|
||||
const float FRAME_TIME = USECS_PER_SECOND / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
||||
|
@ -588,8 +563,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
|||
// parse the identity packet and update the change timestamp if appropriate
|
||||
bool identityChanged = false;
|
||||
bool displayNameChanged = false;
|
||||
bool skeletonModelUrlChanged = false;
|
||||
avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged);
|
||||
avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
||||
|
||||
if (identityChanged) {
|
||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||
|
@ -597,9 +571,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
|||
if (displayNameChanged) {
|
||||
nodeData->setAvatarSessionDisplayNameMustChange(true);
|
||||
}
|
||||
if (skeletonModelUrlChanged && !_avatarWhitelist.isEmpty()) {
|
||||
nodeData->setAvatarSkeletonModelUrlMustChange(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -992,20 +963,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight
|
||||
<< "and a maximum avatar height of" << _domainMaximumHeight;
|
||||
|
||||
const QString AVATAR_WHITELIST_DEFAULT{ "" };
|
||||
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
||||
_avatarWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION].toString(AVATAR_WHITELIST_DEFAULT).split(',', QString::KeepEmptyParts);
|
||||
_slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION]
|
||||
.toString().split(',', QString::KeepEmptyParts);
|
||||
|
||||
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
|
||||
_replacementAvatar = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION].toString(REPLACEMENT_AVATAR_DEFAULT);
|
||||
_slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION]
|
||||
.toString();
|
||||
|
||||
if ((_avatarWhitelist.count() == 1) && _avatarWhitelist[0].isEmpty()) {
|
||||
_avatarWhitelist.clear(); // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok).
|
||||
if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) {
|
||||
// KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok).
|
||||
_slaveSharedData.skeletonURLWhitelist.clear();
|
||||
}
|
||||
|
||||
if (_avatarWhitelist.isEmpty()) {
|
||||
if (_slaveSharedData.skeletonURLWhitelist.isEmpty()) {
|
||||
qCDebug(avatars) << "All avatars are allowed.";
|
||||
} else {
|
||||
qCDebug(avatars) << "Avatars other than" << _avatarWhitelist << "will be replaced by" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
|
||||
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,6 @@ private slots:
|
|||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||
void start();
|
||||
|
||||
|
||||
private:
|
||||
AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node);
|
||||
std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp);
|
||||
|
@ -69,11 +68,6 @@ private:
|
|||
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||
|
||||
void manageIdentityData(const SharedNodePointer& node);
|
||||
bool isAvatarInWhitelist(const QUrl& url);
|
||||
|
||||
const QString REPLACEMENT_AVATAR_DEFAULT{ "" };
|
||||
QStringList _avatarWhitelist { };
|
||||
QString _replacementAvatar { REPLACEMENT_AVATAR_DEFAULT };
|
||||
|
||||
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
|
||||
|
||||
|
@ -83,7 +77,6 @@ private:
|
|||
float _trailingMixRatio { 0.0f };
|
||||
float _throttlingRatio { 0.0f };
|
||||
|
||||
|
||||
int _sumListeners { 0 };
|
||||
int _numStatFrames { 0 };
|
||||
int _numTightLoopFrames { 0 };
|
||||
|
@ -127,6 +120,7 @@ private:
|
|||
RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs
|
||||
|
||||
AvatarMixerSlavePool _slavePool;
|
||||
SlaveSharedData _slaveSharedData;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixer_h
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include "AvatarMixerSlave.h"
|
||||
|
||||
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
|
||||
NodeData(nodeID),
|
||||
_receivedSimpleTraitVersions(AvatarTraits::SimpleTraitTypes.size())
|
||||
|
@ -48,7 +50,7 @@ void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message,
|
|||
_packetQueue.push(message);
|
||||
}
|
||||
|
||||
int AvatarMixerClientData::processPackets() {
|
||||
int AvatarMixerClientData::processPackets(SlaveSharedData* slaveSharedData) {
|
||||
int packetsProcessed = 0;
|
||||
SharedNodePointer node = _packetQueue.node;
|
||||
assert(_packetQueue.empty() || node);
|
||||
|
@ -64,7 +66,7 @@ int AvatarMixerClientData::processPackets() {
|
|||
parseData(*packet);
|
||||
break;
|
||||
case PacketType::SetAvatarTraits:
|
||||
processSetTraitsMessage(*packet);
|
||||
processSetTraitsMessage(*packet, slaveSharedData, *node);
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
|
@ -92,7 +94,7 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
|||
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) {
|
||||
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, SlaveSharedData* slaveSharedData, Node& sendingNode) {
|
||||
// pull the trait version from the message
|
||||
AvatarTraits::TraitVersion packetTraitVersion;
|
||||
message.readPrimitive(&packetTraitVersion);
|
||||
|
@ -111,6 +113,11 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) {
|
|||
if (packetTraitVersion > _receivedSimpleTraitVersions[traitType]) {
|
||||
_avatar->processTrait(traitType, message.readWithoutCopy(traitSize));
|
||||
_receivedSimpleTraitVersions[traitType] = packetTraitVersion;
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
|
||||
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);
|
||||
}
|
||||
|
||||
anyTraitsChanged = true;
|
||||
} else {
|
||||
|
@ -123,6 +130,46 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message) {
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(SlaveSharedData *slaveSharedData, Node& sendingNode,
|
||||
AvatarTraits::TraitVersion traitVersion) {
|
||||
const auto& whitelist = slaveSharedData->skeletonURLWhitelist;
|
||||
|
||||
if (!whitelist.isEmpty()) {
|
||||
bool inWhitelist = false;
|
||||
auto avatarURL = _avatar->getSkeletonModelURL();
|
||||
|
||||
// The avatar is in the whitelist if:
|
||||
// 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND
|
||||
// 2. The avatar's URL's path starts with the path of that same URL in the whitelist
|
||||
for (const auto& whiteListedPrefix : whitelist) {
|
||||
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
|
||||
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
|
||||
if (avatarURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
|
||||
avatarURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
|
||||
inWhitelist = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inWhitelist) {
|
||||
// we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change
|
||||
_avatar->setSkeletonModelURL(slaveSharedData->skeletonReplacementURL);
|
||||
|
||||
qDebug() << "Sending overwritten" << _avatar->getSkeletonModelURL() << "back to sending avatar";
|
||||
|
||||
auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true);
|
||||
|
||||
// the returned set traits packet uses the trait version from the incoming packet
|
||||
// so the client knows they should not overwrite if they have since changed the trait
|
||||
_avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(packet), sendingNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
|
||||
// return the matching PacketSequenceNumber, or the default if we don't have it
|
||||
auto nodeMatch = _lastBroadcastTimes.find(nodeUUID);
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
|
||||
const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
|
||||
|
||||
struct SlaveSharedData;
|
||||
|
||||
class AvatarMixerClientData : public NodeData {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -66,8 +68,6 @@ public:
|
|||
void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
|
||||
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
|
||||
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
|
||||
bool getAvatarSkeletonModelUrlMustChange() const { return _avatarSkeletonModelUrlMustChange; }
|
||||
void setAvatarSkeletonModelUrlMustChange(bool set = true) { _avatarSkeletonModelUrlMustChange = set; }
|
||||
|
||||
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
||||
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
||||
|
@ -119,9 +119,11 @@ public:
|
|||
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
|
||||
|
||||
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
||||
int processPackets(); // returns number of packets processed
|
||||
int processPackets(SlaveSharedData* slaveSharedData); // returns number of packets processed
|
||||
|
||||
void processSetTraitsMessage(ReceivedMessage& message);
|
||||
void processSetTraitsMessage(ReceivedMessage& message, SlaveSharedData* slaveSharedData, Node& sendingNode);
|
||||
void checkSkeletonURLAgainstWhitelist(SlaveSharedData* slaveSharedData, Node& sendingNode,
|
||||
AvatarTraits::TraitVersion traitVersion);
|
||||
|
||||
using TraitsCheckTimestamp = std::chrono::steady_clock::time_point;
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) {
|
|||
auto nodeData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
if (nodeData) {
|
||||
_stats.nodesProcessed++;
|
||||
_stats.packetsProcessed += nodeData->processPackets();
|
||||
_stats.packetsProcessed += nodeData->processPackets(_sharedData);
|
||||
}
|
||||
auto end = usecTimestampNow();
|
||||
_stats.processIncomingPacketsElapsedTime += (end - start);
|
||||
|
@ -108,20 +108,10 @@ void AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* liste
|
|||
if (lastReceivedVersion > lastSentVersion) {
|
||||
// there is an update to this trait, add it to the traits packet
|
||||
|
||||
// write the trait type and the trait version
|
||||
traitsPacketList.writePrimitive(traitType);
|
||||
traitsPacketList.writePrimitive(lastReceivedVersion);
|
||||
|
||||
// update the last sent version since we're adding this to the packet
|
||||
// update the last sent version
|
||||
listeningNodeData->setLastSentSimpleTraitVersion(otherNodeLocalID, traitType, lastReceivedVersion);
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// get an encoded version of the URL, write its size and then the data itself
|
||||
auto encodedSkeletonURL = sendingAvatar->getSkeletonModelURL().toEncoded();
|
||||
|
||||
traitsPacketList.writePrimitive(uint16_t(encodedSkeletonURL.size()));
|
||||
traitsPacketList.write(encodedSkeletonURL);
|
||||
}
|
||||
sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -78,11 +78,16 @@ public:
|
|||
jobElapsedTime += rhs.jobElapsedTime;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct SlaveSharedData {
|
||||
QStringList skeletonURLWhitelist;
|
||||
QUrl skeletonReplacementURL;
|
||||
};
|
||||
|
||||
class AvatarMixerSlave {
|
||||
public:
|
||||
AvatarMixerSlave(SlaveSharedData* sharedData) : _sharedData(sharedData) {};
|
||||
using ConstIter = NodeList::const_iterator;
|
||||
|
||||
void configure(ConstIter begin, ConstIter end);
|
||||
|
@ -115,6 +120,7 @@ private:
|
|||
float _throttlingRatio { 0.0f };
|
||||
|
||||
AvatarMixerSlaveStats _stats;
|
||||
SlaveSharedData* _sharedData;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixerSlave_h
|
||||
|
|
|
@ -168,7 +168,7 @@ void AvatarMixerSlavePool::resize(int numThreads) {
|
|||
if (numThreads > _numThreads) {
|
||||
// start new slaves
|
||||
for (int i = 0; i < numThreads - _numThreads; ++i) {
|
||||
auto slave = new AvatarMixerSlaveThread(*this);
|
||||
auto slave = new AvatarMixerSlaveThread(*this, _slaveSharedData);
|
||||
slave->start();
|
||||
_slaves.emplace_back(slave);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave {
|
|||
using Lock = std::unique_lock<Mutex>;
|
||||
|
||||
public:
|
||||
AvatarMixerSlaveThread(AvatarMixerSlavePool& pool) : _pool(pool) {}
|
||||
AvatarMixerSlaveThread(AvatarMixerSlavePool& pool, SlaveSharedData* slaveSharedData) :
|
||||
AvatarMixerSlave(slaveSharedData), _pool(pool) {};
|
||||
|
||||
void run() override final;
|
||||
|
||||
|
@ -59,7 +60,8 @@ class AvatarMixerSlavePool {
|
|||
public:
|
||||
using ConstIter = NodeList::const_iterator;
|
||||
|
||||
AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
|
||||
AvatarMixerSlavePool(SlaveSharedData* slaveSharedData, int numThreads = QThread::idealThreadCount()) :
|
||||
_slaveSharedData(slaveSharedData) { setNumThreads(numThreads); }
|
||||
~AvatarMixerSlavePool() { resize(0); }
|
||||
|
||||
// Jobs the slave pool can do...
|
||||
|
@ -98,6 +100,8 @@ private:
|
|||
Queue _queue;
|
||||
ConstIter _begin;
|
||||
ConstIter _end;
|
||||
|
||||
SlaveSharedData* _slaveSharedData;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarMixerSlavePool_h
|
||||
|
|
|
@ -6393,8 +6393,8 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
if (_avatarOverrideUrl.isValid()) {
|
||||
getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl);
|
||||
}
|
||||
static const QUrl empty{};
|
||||
if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) {
|
||||
|
||||
if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->getSkeletonModelURL()) {
|
||||
getMyAvatar()->resetFullAvatarURL();
|
||||
}
|
||||
getMyAvatar()->markIdentityDataChanged();
|
||||
|
|
|
@ -1750,12 +1750,6 @@ glm::quat AvatarData::getOrientationOutbound() const {
|
|||
return (getLocalOrientation());
|
||||
}
|
||||
|
||||
static const QUrl emptyURL("");
|
||||
QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
||||
// We don't put file urls on the wire, but instead convert to empty.
|
||||
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
|
||||
}
|
||||
|
||||
void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged,
|
||||
bool& displayNameChanged) {
|
||||
|
||||
|
@ -1836,6 +1830,27 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, int64_t traitVersion) {
|
||||
destination.writePrimitive(traitType);
|
||||
|
||||
if (traitVersion > 0) {
|
||||
AvatarTraits::TraitVersion typedVersion = traitVersion;
|
||||
destination.writePrimitive(typedVersion);
|
||||
}
|
||||
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
QByteArray encodedSkeletonURL;
|
||||
if (_skeletonModelURL.scheme() != "file" && _skeletonModelURL.scheme() != "qrc") {
|
||||
encodedSkeletonURL = _skeletonModelURL.toEncoded();
|
||||
}
|
||||
|
||||
AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
|
||||
destination.writePrimitive(encodedURLSize);
|
||||
|
||||
destination.write(encodedSkeletonURL);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
|
||||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// get the URL from the binary data
|
||||
|
|
|
@ -426,7 +426,6 @@ public:
|
|||
virtual ~AvatarData();
|
||||
|
||||
static const QUrl& defaultFullAvatarModelUrl();
|
||||
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
|
||||
|
||||
virtual bool isMyAvatar() const { return false; }
|
||||
|
||||
|
@ -958,6 +957,7 @@ public:
|
|||
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
|
||||
void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged);
|
||||
|
||||
void packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination, int64_t traitVersion = -1);
|
||||
void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
|
||||
|
||||
QByteArray identityByteArray(bool setIsReplicated = false) const;
|
||||
|
|
|
@ -29,6 +29,9 @@ namespace AvatarTraits {
|
|||
using TraitVersion = uint32_t;
|
||||
const TraitVersion DEFAULT_TRAIT_VERSION = 0;
|
||||
|
||||
using NullableTraitVersion = int64_t;
|
||||
const NullableTraitVersion NULL_TRAIT_VERSION = -1;
|
||||
|
||||
using TraitWireSize = uint16_t;
|
||||
|
||||
using SimpleTraitVersions = std::vector<TraitVersion>;
|
||||
|
|
|
@ -27,6 +27,8 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
|
|||
resetForNewMixer();
|
||||
}
|
||||
});
|
||||
|
||||
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride");
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::resetForNewMixer() {
|
||||
|
@ -63,19 +65,11 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
|
||||
if (_performInitialSend || changedTraitsCopy.count(AvatarTraits::SkeletonModelURL)) {
|
||||
traitsPacketList->startSegment();
|
||||
|
||||
traitsPacketList->writePrimitive(AvatarTraits::SkeletonModelURL);
|
||||
|
||||
auto encodedSkeletonURL = _owningAvatar->getSkeletonModelURL().toEncoded();
|
||||
|
||||
AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
|
||||
traitsPacketList->writePrimitive(encodedURLSize);
|
||||
|
||||
qDebug() << "Sending trait of size" << encodedURLSize;
|
||||
|
||||
traitsPacketList->write(encodedSkeletonURL);
|
||||
|
||||
_owningAvatar->packTrait(AvatarTraits::SkeletonModelURL, *traitsPacketList);
|
||||
traitsPacketList->endSegment();
|
||||
|
||||
// keep track of our skeleton version in case we get an override back
|
||||
_currentSkeletonVersion = _currentTraitVersion;
|
||||
}
|
||||
|
||||
nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer);
|
||||
|
@ -84,3 +78,33 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
|||
_performInitialSend = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
if (sendingNode->getType() == NodeType::AvatarMixer) {
|
||||
while (message->getBytesLeftToRead()) {
|
||||
AvatarTraits::TraitType traitType;
|
||||
message->readPrimitive(&traitType);
|
||||
|
||||
AvatarTraits::TraitVersion traitVersion;
|
||||
message->readPrimitive(&traitVersion);
|
||||
|
||||
AvatarTraits::TraitWireSize traitBinarySize;
|
||||
message->readPrimitive(&traitBinarySize);
|
||||
|
||||
// only accept an override if this is for a trait type we override
|
||||
// and the version matches what we last sent for skeleton
|
||||
if (traitType == AvatarTraits::SkeletonModelURL
|
||||
&& traitVersion == _currentSkeletonVersion
|
||||
&& !hasTraitChanged(AvatarTraits::SkeletonModelURL)) {
|
||||
// override the skeleton URL but do not mark the trait as having changed
|
||||
// so that we don't unecessarily sent a new trait packet to the mixer with the overriden URL
|
||||
auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize));
|
||||
_owningAvatar->setSkeletonModelURL(encodedSkeletonURL);
|
||||
|
||||
_changedTraits.erase(AvatarTraits::SkeletonModelURL);
|
||||
} else {
|
||||
message->seek(message->getPosition() + traitBinarySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,15 @@
|
|||
#ifndef hifi_ClientTraitsHandler_h
|
||||
#define hifi_ClientTraitsHandler_h
|
||||
|
||||
#include <set>
|
||||
#include <ReceivedMessage.h>
|
||||
|
||||
#include "AvatarTraits.h"
|
||||
#include "Node.h"
|
||||
|
||||
class AvatarData;
|
||||
|
||||
class ClientTraitsHandler {
|
||||
class ClientTraitsHandler : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ClientTraitsHandler(AvatarData* owningAvatar);
|
||||
|
||||
|
@ -31,11 +33,17 @@ public:
|
|||
|
||||
void resetForNewMixer();
|
||||
|
||||
public slots:
|
||||
void processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
AvatarData* _owningAvatar;
|
||||
|
||||
AvatarTraits::TraitTypeSet _changedTraits;
|
||||
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
|
||||
|
||||
AvatarTraits::NullableTraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION };
|
||||
|
||||
bool _performInitialSend { false };
|
||||
};
|
||||
|
||||
|
|
39
libraries/networking/src/ExtendedIODevice.h
Normal file
39
libraries/networking/src/ExtendedIODevice.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// ExtendedIODevice.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 8/7/18.
|
||||
// Copyright 2018 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_ExtendedIODevice_h
|
||||
#define hifi_ExtendedIODevice_h
|
||||
|
||||
#include <QtCore/QIODevice>
|
||||
|
||||
class ExtendedIODevice : public QIODevice {
|
||||
public:
|
||||
ExtendedIODevice(QObject* parent = nullptr) : QIODevice(parent) {};
|
||||
|
||||
template<typename T> qint64 peekPrimitive(T* data);
|
||||
template<typename T> qint64 readPrimitive(T* data);
|
||||
template<typename T> qint64 writePrimitive(const T& data);
|
||||
};
|
||||
|
||||
template<typename T> qint64 ExtendedIODevice::peekPrimitive(T* data) {
|
||||
return peek(reinterpret_cast<char*>(data), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> qint64 ExtendedIODevice::readPrimitive(T* data) {
|
||||
return read(reinterpret_cast<char*>(data), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> qint64 ExtendedIODevice::writePrimitive(const T& data) {
|
||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
||||
return write(reinterpret_cast<const char*>(&data), sizeof(T));
|
||||
}
|
||||
|
||||
#endif // hifi_ExtendedIODevice_h
|
|
@ -78,7 +78,7 @@ BasePacket::BasePacket(std::unique_ptr<char[]> data, qint64 size, const HifiSock
|
|||
}
|
||||
|
||||
BasePacket::BasePacket(const BasePacket& other) :
|
||||
QIODevice()
|
||||
ExtendedIODevice()
|
||||
{
|
||||
*this = other;
|
||||
}
|
||||
|
|
|
@ -16,16 +16,15 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <QtCore/QIODevice>
|
||||
|
||||
#include <PortableHighResolutionClock.h>
|
||||
|
||||
#include "../HifiSockAddr.h"
|
||||
#include "Constants.h"
|
||||
#include "../ExtendedIODevice.h"
|
||||
|
||||
namespace udt {
|
||||
|
||||
class BasePacket : public QIODevice {
|
||||
class BasePacket : public ExtendedIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const qint64 PACKET_WRITE_ERROR;
|
||||
|
@ -85,10 +84,6 @@ public:
|
|||
|
||||
void setReceiveTime(p_high_resolution_clock::time_point receiveTime) { _receiveTime = receiveTime; }
|
||||
p_high_resolution_clock::time_point getReceiveTime() const { return _receiveTime; }
|
||||
|
||||
template<typename T> qint64 peekPrimitive(T* data);
|
||||
template<typename T> qint64 readPrimitive(T* data);
|
||||
template<typename T> qint64 writePrimitive(const T& data);
|
||||
|
||||
protected:
|
||||
BasePacket(qint64 size);
|
||||
|
@ -116,19 +111,6 @@ protected:
|
|||
|
||||
p_high_resolution_clock::time_point _receiveTime; // captures the time the packet received (only used on receiving end)
|
||||
};
|
||||
|
||||
template<typename T> qint64 BasePacket::peekPrimitive(T* data) {
|
||||
return peek(reinterpret_cast<char*>(data), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> qint64 BasePacket::readPrimitive(T* data) {
|
||||
return read(reinterpret_cast<char*>(data), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> qint64 BasePacket::writePrimitive(const T& data) {
|
||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
||||
return write(reinterpret_cast<const char*>(&data), sizeof(T));
|
||||
}
|
||||
|
||||
} // namespace udt
|
||||
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include <QtCore/QIODevice>
|
||||
|
||||
#include "../ExtendedIODevice.h"
|
||||
#include "Packet.h"
|
||||
#include "PacketHeaders.h"
|
||||
|
||||
|
@ -25,7 +24,7 @@ namespace udt {
|
|||
|
||||
class Packet;
|
||||
|
||||
class PacketList : public QIODevice {
|
||||
class PacketList : public ExtendedIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using MessageNumber = uint32_t;
|
||||
|
@ -59,9 +58,6 @@ public:
|
|||
virtual bool isSequential() const override { return false; }
|
||||
virtual qint64 size() const override { return getDataSize(); }
|
||||
|
||||
template<typename T> qint64 readPrimitive(T* data);
|
||||
template<typename T> qint64 writePrimitive(const T& data);
|
||||
|
||||
qint64 writeString(const QString& string);
|
||||
|
||||
protected:
|
||||
|
@ -105,16 +101,6 @@ private:
|
|||
QByteArray _extendedHeader;
|
||||
};
|
||||
|
||||
template <typename T> qint64 PacketList::readPrimitive(T* data) {
|
||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
||||
return read(reinterpret_cast<char*>(data), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T> qint64 PacketList::writePrimitive(const T& data) {
|
||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
||||
return write(reinterpret_cast<const char*>(&data), sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> std::unique_ptr<T> PacketList::takeFront() {
|
||||
static_assert(std::is_base_of<Packet, T>::value, "T must derive from Packet.");
|
||||
|
||||
|
|
Loading…
Reference in a new issue