handle whitelist avatar URL override via traits

This commit is contained in:
Stephen Birarda 2018-08-07 18:08:10 -07:00
parent a80d19a44a
commit be7eb57205
20 changed files with 224 additions and 151 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

@ -78,7 +78,7 @@ BasePacket::BasePacket(std::unique_ptr<char[]> data, qint64 size, const HifiSock
}
BasePacket::BasePacket(const BasePacket& other) :
QIODevice()
ExtendedIODevice()
{
*this = other;
}

View file

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

View file

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