sanitiy checking when unpacking AvatarData update

This commit is contained in:
Andrew Meadows 2014-03-25 09:55:43 -07:00
parent 24b80fca81
commit 17d878bc94
2 changed files with 157 additions and 61 deletions

View file

@ -25,7 +25,7 @@
#include "AvatarData.h" #include "AvatarData.h"
quint64 DEFAULT_FILTERED_LOG_EXPIRY = 20 * USECS_PER_SECOND; quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND;
using namespace std; using namespace std;
@ -46,7 +46,7 @@ AvatarData::AvatarData() :
_displayNameTargetAlpha(0.0f), _displayNameTargetAlpha(0.0f),
_displayNameAlpha(0.0f), _displayNameAlpha(0.0f),
_billboard(), _billboard(),
_debugLogExpiry(0) _errorLogExpiry(0)
{ {
} }
@ -178,6 +178,14 @@ QByteArray AvatarData::toByteArray() {
return avatarDataByteArray.left(destinationBuffer - startPosition); return avatarDataByteArray.left(destinationBuffer - startPosition);
} }
bool AvatarData::shouldLogError(const quint64& now) {
if (now > _errorLogExpiry) {
_errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
return true;
}
return false;
}
// read data in packet starting at byte offset and return number of bytes parsed // read data in packet starting at byte offset and return number of bytes parsed
int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
// lazily allocate memory for HeadData in case we're not an Avatar instance // lazily allocate memory for HeadData in case we're not an Avatar instance
@ -192,37 +200,80 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(packet.data()) + offset; const unsigned char* startPosition = reinterpret_cast<const unsigned char*>(packet.data()) + offset;
const unsigned char* sourceBuffer = startPosition; const unsigned char* sourceBuffer = startPosition;
quint64 now = usecTimestampNow();
// 50 bytes of "plain old data" (POD) // The absolute minimum size of the update data is as follows:
// 1 byte for messageSize (0) // 50 bytes of "plain old data" {
// 1 byte for pupilSize // position = 12 bytes
// 1 byte for numJoints (0) // bodyYaw = 2 (compressed float)
// bodyPitch = 2 (compressed float)
// bodyRoll = 2 (compressed float)
// targetScale = 2 (compressed float)
// headYaw = 2 (compressed float)
// headPitch = 2 (compressed float)
// headRoll = 2 (compressed float)
// leanSideways = 4
// leanForward = 4
// lookAt = 12
// audioLoudness = 4
// }
// + 1 byte for messageSize (0)
// + 1 byte for pupilSize
// + 1 byte for numJoints (0)
// = 53 bytes
int minPossibleSize = 53; int minPossibleSize = 53;
int maxAvailableSize = packet.size() - offset; int maxAvailableSize = packet.size() - offset;
if (minPossibleSize > maxAvailableSize) { if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end if (shouldLogError(now)) {
quint64 now = usecTimestampNow(); qDebug() << "Malformed AvatarData packet at the start; "
if (now > _debugLogExpiry) { << " displayName = '" << _displayName << "'"
qDebug() << "Malformed AvatarData packet at the start: minPossibleSize = " << minPossibleSize << " minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize; << " maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
} }
// this packet is malformed so we report all bytes as consumed
return maxAvailableSize; return maxAvailableSize;
} }
{ // Body world position, rotation, and scale { // Body world position, rotation, and scale
// position // position
memcpy(&_position, sourceBuffer, sizeof(float) * 3); glm::vec3 position;
sourceBuffer += sizeof(float) * 3; memcpy(&position, sourceBuffer, sizeof(position));
sourceBuffer += sizeof(position);
if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::position; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_position = position;
// rotation (NOTE: This needs to become a quaternion to save two bytes) // rotation (NOTE: This needs to become a quaternion to save two bytes)
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw); float yaw, pitch, roll;
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &yaw);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &pitch);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &roll);
if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::yaw,pitch,roll; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_bodyYaw = yaw;
_bodyPitch = pitch;
_bodyRoll = roll;
// scale // scale
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale); float scale;
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, scale);
if (glm::isnan(scale)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::scale; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_targetScale = scale;
} // 20 bytes } // 20 bytes
{ // Head rotation { // Head rotation
@ -231,6 +282,12 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch);
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
if (glm::isnan(headYaw) || glm::isnan(headPitch) || glm::isnan(headRoll)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::headYaw,headPitch,headRoll; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_headData->setYaw(headYaw); _headData->setYaw(headYaw);
_headData->setPitch(headPitch); _headData->setPitch(headPitch);
_headData->setRoll(headRoll); _headData->setRoll(headRoll);
@ -238,33 +295,57 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
// Head lean (relative to pelvis) // Head lean (relative to pelvis)
{ {
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways)); float leanSideways, leanForward;
memcpy(&leanSideways, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward)); memcpy(&leanForward, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
if (glm::isnan(leanSideways) || glm::isnan(leanForward)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::leanSideways,leanForward; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_headData->_leanSideways = leanSideways;
_headData->_leanForward = leanForward;
} // 8 bytes } // 8 bytes
{ // Lookat Position { // Lookat Position
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition)); glm::vec3 lookAt;
sourceBuffer += sizeof(_headData->_lookAtPosition); memcpy(&lookAt, sourceBuffer, sizeof(lookAt));
sourceBuffer += sizeof(lookAt);
if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::lookAt; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_headData->_lookAtPosition = lookAt;
} // 12 bytes } // 12 bytes
{ // AudioLoudness { // AudioLoudness
// Instantaneous audio loudness (used to drive facial animation) // Instantaneous audio loudness (used to drive facial animation)
memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float)); float audioLoudness;
memcpy(&audioLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
if (glm::isnan(audioLoudness)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::audioLoudness; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_headData->_audioLoudness = audioLoudness;
} // 4 bytes } // 4 bytes
// chat // chat
int chatMessageSize = *sourceBuffer++; int chatMessageSize = *sourceBuffer++;
minPossibleSize += chatMessageSize; minPossibleSize += chatMessageSize;
if (minPossibleSize > maxAvailableSize) { if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end if (shouldLogError(now)) {
quint64 now = usecTimestampNow(); qDebug() << "Malformed AvatarData packet before ChatMessage;"
if (now > _debugLogExpiry) { << " displayName = '" << _displayName << "'"
qDebug() << "Malformed AvatarData packet before ChatMessage: minPossibleSize = " << minPossibleSize << " minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize; << " maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
} }
return maxAvailableSize; return maxAvailableSize;
} }
@ -287,40 +368,52 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
_isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED); _isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED);
if (_headData->_isFaceshiftConnected) { if (_headData->_isFaceshiftConnected) {
minPossibleSize += 4 * sizeof(float) + 1; // four floats + one byte for blendDataSize float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift;
minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift);
minPossibleSize++; // one byte for blendDataSize
if (minPossibleSize > maxAvailableSize) { if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end if (shouldLogError(now)) {
quint64 now = usecTimestampNow(); qDebug() << "Malformed AvatarData packet after BitItems;"
if (now > _debugLogExpiry) { << " displayName = '" << _displayName << "'"
qDebug() << "Malformed AvatarData packet after BitItems: minPossibleSize = " << minPossibleSize << " minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize; << " maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
} }
return maxAvailableSize; return maxAvailableSize;
} }
// unpack face data // unpack face data
memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float)); memcpy(&leftEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float)); memcpy(&rightEyeBlink, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float)); memcpy(&averageLoudness, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float)); memcpy(&browAudioLift, sourceBuffer, sizeof(float));
sourceBuffer += sizeof(float); sourceBuffer += sizeof(float);
if (glm::isnan(leftEyeBlink) || glm::isnan(rightEyeBlink)
|| glm::isnan(averageLoudness) || glm::isnan(browAudioLift)) {
if (shouldLogError(now)) {
qDebug() << "Discard nan AvatarData::faceData; displayName = '" << _displayName << "'";
}
return maxAvailableSize;
}
_headData->_leftEyeBlink = leftEyeBlink;
_headData->_rightEyeBlink = rightEyeBlink;
_headData->_averageLoudness = averageLoudness;
_headData->_browAudioLift = browAudioLift;
int numCoefficients = (int)(*sourceBuffer++); int numCoefficients = (int)(*sourceBuffer++);
int blendDataSize = numCoefficients * sizeof(float); int blendDataSize = numCoefficients * sizeof(float);
minPossibleSize += blendDataSize; minPossibleSize += blendDataSize;
if (minPossibleSize > maxAvailableSize) { if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end if (shouldLogError(now)) {
quint64 now = usecTimestampNow(); qDebug() << "Malformed AvatarData packet after Blendshapes;"
if (now > _debugLogExpiry) { << " displayName = '" << _displayName << "'"
qDebug() << "Malformed AvatarData packet after Blendshapes: minPossibleSize = " << minPossibleSize << " minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize; << " maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
} }
return maxAvailableSize; return maxAvailableSize;
} }
@ -339,15 +432,14 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
// joint data // joint data
int numJoints = *sourceBuffer++; int numJoints = *sourceBuffer++;
int bytesOfValidity = (int)ceil((float)numJoints / 8.f); int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE);
minPossibleSize += bytesOfValidity; minPossibleSize += bytesOfValidity;
if (minPossibleSize > maxAvailableSize) { if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end if (shouldLogError(now)) {
quint64 now = usecTimestampNow(); qDebug() << "Malformed AvatarData packet after JointValidityBits;"
if (now > _debugLogExpiry) { << " displayName = '" << _displayName << "'"
qDebug() << "Malformed AvatarData packet after JointValidityBits: minPossibleSize = " << minPossibleSize << " minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize; << " maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
} }
return maxAvailableSize; return maxAvailableSize;
} }
@ -370,14 +462,15 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
} }
// 1 + bytesOfValidity bytes // 1 + bytesOfValidity bytes
minPossibleSize += numValidJoints * 8; // 8 bytes per quaternion // each joint rotation component is stored in two bytes (sizeof(uint16_t))
int COMPONENTS_PER_QUATERNION = 4;
minPossibleSize += numValidJoints * COMPONENTS_PER_QUATERNION * sizeof(uint16_t);
if (minPossibleSize > maxAvailableSize) { if (minPossibleSize > maxAvailableSize) {
// this packet is malformed so we pretend to read to the end if (shouldLogError(now)) {
quint64 now = usecTimestampNow(); qDebug() << "Malformed AvatarData packet after JointData;"
if (now > _debugLogExpiry) { << " displayName = '" << _displayName << "'"
qDebug() << "Malformed AvatarData packet after JointData: minPossibleSize = " << minPossibleSize << " minPossibleSize = " << minPossibleSize
<< " but maxAvailableSize = " << maxAvailableSize; << " maxAvailableSize = " << maxAvailableSize;
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
} }
return maxAvailableSize; return maxAvailableSize;
} }

View file

@ -105,6 +105,9 @@ public:
QByteArray toByteArray(); QByteArray toByteArray();
/// \return true if an error should be logged
bool shouldLogError(const quint64& now);
/// \param packet byte array of data /// \param packet byte array of data
/// \param offset number of bytes into packet where data starts /// \param offset number of bytes into packet where data starts
/// \return number of bytes parsed /// \return number of bytes parsed
@ -255,7 +258,7 @@ protected:
static QNetworkAccessManager* networkAccessManager; static QNetworkAccessManager* networkAccessManager;
quint64 _debugLogExpiry; quint64 _errorLogExpiry; ///< time in future when to log an error
private: private:
// privatize the copy constructor and assignment operator so they cannot be called // privatize the copy constructor and assignment operator so they cannot be called