mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 22:17:57 +02:00
datalength sanity checking for AvatarData packet
This commit is contained in:
parent
6440dd1b5f
commit
22aa9b075d
5 changed files with 209 additions and 92 deletions
|
@ -133,7 +133,7 @@ public:
|
||||||
|
|
||||||
void setShowDisplayName(bool showDisplayName);
|
void setShowDisplayName(bool showDisplayName);
|
||||||
|
|
||||||
int parseDataAtOffset(const QByteArray& packet, int offset);
|
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
|
||||||
|
|
||||||
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
|
static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2);
|
||||||
|
|
||||||
|
|
|
@ -551,6 +551,14 @@ void MyAvatar::loadData(QSettings* settings) {
|
||||||
settings->endGroup();
|
settings->endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) {
|
||||||
|
qDebug() << "Error: ignoring update packet for MyAvatar"
|
||||||
|
<< " packetLength = " << packet.size()
|
||||||
|
<< " offset = " << offset;
|
||||||
|
// this packet is just bad, so we pretend that we unpacked it ALL
|
||||||
|
return packet.size() - offset;
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::sendKillAvatar() {
|
void MyAvatar::sendKillAvatar() {
|
||||||
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
|
QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar);
|
||||||
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
|
NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer);
|
||||||
|
|
|
@ -71,6 +71,8 @@ public:
|
||||||
void jump() { _shouldJump = true; };
|
void jump() { _shouldJump = true; };
|
||||||
|
|
||||||
bool isMyAvatar() { return true; }
|
bool isMyAvatar() { return true; }
|
||||||
|
|
||||||
|
virtual int parseDataAtOffset(const QByteArray& packet, int offset);
|
||||||
|
|
||||||
static void sendKillAvatar();
|
static void sendKillAvatar();
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <QtCore/QDataStream>
|
#include <QtCore/QDataStream>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
|
#include <QtCore/QUuid>
|
||||||
#include <QtNetwork/QNetworkAccessManager>
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
#include <QtNetwork/QNetworkRequest>
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
@ -24,6 +25,8 @@
|
||||||
|
|
||||||
#include "AvatarData.h"
|
#include "AvatarData.h"
|
||||||
|
|
||||||
|
quint64 DEFAULT_FILTERED_LOG_EXPIRY = 20 * USECS_PER_SECOND;
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
QNetworkAccessManager* AvatarData::networkAccessManager = NULL;
|
QNetworkAccessManager* AvatarData::networkAccessManager = NULL;
|
||||||
|
@ -42,7 +45,8 @@ AvatarData::AvatarData() :
|
||||||
_displayNameBoundingRect(),
|
_displayNameBoundingRect(),
|
||||||
_displayNameTargetAlpha(0.0f),
|
_displayNameTargetAlpha(0.0f),
|
||||||
_displayNameAlpha(0.0f),
|
_displayNameAlpha(0.0f),
|
||||||
_billboard()
|
_billboard(),
|
||||||
|
_debugLogExpiry(0)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -176,7 +180,6 @@ QByteArray AvatarData::toByteArray() {
|
||||||
|
|
||||||
// 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
|
||||||
if (!_headData) {
|
if (!_headData) {
|
||||||
_headData = new HeadData(this);
|
_headData = new HeadData(this);
|
||||||
|
@ -189,102 +192,204 @@ 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;
|
||||||
|
|
||||||
|
// 50 bytes of "plain old data" (POD)
|
||||||
|
// 1 byte for messageSize (0)
|
||||||
|
// 1 byte for pupilSize
|
||||||
|
// 1 byte for numJoints (0)
|
||||||
|
int minPossibleSize = 53;
|
||||||
|
|
||||||
// Body world position
|
int maxAvailableSize = packet.size() - offset;
|
||||||
memcpy(&_position, sourceBuffer, sizeof(float) * 3);
|
if (minPossibleSize > maxAvailableSize) {
|
||||||
sourceBuffer += sizeof(float) * 3;
|
// this packet is malformed so we pretend to read to the end
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
// Body rotation (NOTE: This needs to become a quaternion to save two bytes)
|
if (now > _debugLogExpiry) {
|
||||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw);
|
qDebug() << "Malformed AvatarData packet at the start: minPossibleSize = " << minPossibleSize
|
||||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch);
|
<< " but maxAvailableSize = " << maxAvailableSize;
|
||||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll);
|
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||||
|
}
|
||||||
// Body scale
|
return maxAvailableSize;
|
||||||
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
|
|
||||||
|
|
||||||
// Head rotation (NOTE: This needs to become a quaternion to save two bytes)
|
|
||||||
float headYaw, headPitch, headRoll;
|
|
||||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw);
|
|
||||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch);
|
|
||||||
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
|
|
||||||
_headData->setYaw(headYaw);
|
|
||||||
_headData->setPitch(headPitch);
|
|
||||||
_headData->setRoll(headRoll);
|
|
||||||
|
|
||||||
// Head position relative to pelvis
|
|
||||||
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
|
|
||||||
sourceBuffer += sizeof(float);
|
|
||||||
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
|
|
||||||
sourceBuffer += sizeof(_headData->_leanForward);
|
|
||||||
|
|
||||||
// Lookat Position
|
|
||||||
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
|
|
||||||
sourceBuffer += sizeof(_headData->_lookAtPosition);
|
|
||||||
|
|
||||||
// Instantaneous audio loudness (used to drive facial animation)
|
|
||||||
memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float));
|
|
||||||
sourceBuffer += sizeof(float);
|
|
||||||
|
|
||||||
// the rest is a chat message
|
|
||||||
int chatMessageSize = *sourceBuffer++;
|
|
||||||
_chatMessage = string((char*)sourceBuffer, chatMessageSize);
|
|
||||||
sourceBuffer += chatMessageSize * sizeof(char);
|
|
||||||
|
|
||||||
// voxel sending features...
|
|
||||||
unsigned char bitItems = 0;
|
|
||||||
bitItems = (unsigned char)*sourceBuffer++;
|
|
||||||
|
|
||||||
// key state, stored as a semi-nibble in the bitItems
|
|
||||||
_keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT);
|
|
||||||
|
|
||||||
// hand state, stored as a semi-nibble in the bitItems
|
|
||||||
_handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT);
|
|
||||||
|
|
||||||
_headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
|
|
||||||
|
|
||||||
_isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED);
|
|
||||||
|
|
||||||
// If it is connected, pack up the data
|
|
||||||
if (_headData->_isFaceshiftConnected) {
|
|
||||||
memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float));
|
|
||||||
sourceBuffer += sizeof(float);
|
|
||||||
|
|
||||||
memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float));
|
|
||||||
sourceBuffer += sizeof(float);
|
|
||||||
|
|
||||||
memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float));
|
|
||||||
sourceBuffer += sizeof(float);
|
|
||||||
|
|
||||||
memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float));
|
|
||||||
sourceBuffer += sizeof(float);
|
|
||||||
|
|
||||||
_headData->_blendshapeCoefficients.resize(*sourceBuffer++);
|
|
||||||
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer,
|
|
||||||
_headData->_blendshapeCoefficients.size() * sizeof(float));
|
|
||||||
sourceBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // Body world position, rotation, and scale
|
||||||
|
// position
|
||||||
|
memcpy(&_position, sourceBuffer, sizeof(float) * 3);
|
||||||
|
sourceBuffer += sizeof(float) * 3;
|
||||||
|
|
||||||
|
// rotation (NOTE: This needs to become a quaternion to save two bytes)
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw);
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch);
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll);
|
||||||
|
|
||||||
|
// scale
|
||||||
|
sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale);
|
||||||
|
} // 20 bytes
|
||||||
|
|
||||||
// pupil dilation
|
{ // Head rotation
|
||||||
sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f);
|
//(NOTE: This needs to become a quaternion to save two bytes)
|
||||||
|
float headYaw, headPitch, headRoll;
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw);
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch);
|
||||||
|
sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll);
|
||||||
|
_headData->setYaw(headYaw);
|
||||||
|
_headData->setPitch(headPitch);
|
||||||
|
_headData->setRoll(headRoll);
|
||||||
|
} // 6 bytes
|
||||||
|
|
||||||
|
// Head lean (relative to pelvis)
|
||||||
|
{
|
||||||
|
memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
} // 8 bytes
|
||||||
|
|
||||||
|
{ // Lookat Position
|
||||||
|
memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition));
|
||||||
|
sourceBuffer += sizeof(_headData->_lookAtPosition);
|
||||||
|
} // 12 bytes
|
||||||
|
|
||||||
|
{ // AudioLoudness
|
||||||
|
// Instantaneous audio loudness (used to drive facial animation)
|
||||||
|
memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
} // 4 bytes
|
||||||
|
|
||||||
|
// chat
|
||||||
|
int chatMessageSize = *sourceBuffer++;
|
||||||
|
minPossibleSize += chatMessageSize;
|
||||||
|
if (minPossibleSize > maxAvailableSize) {
|
||||||
|
// this packet is malformed so we pretend to read to the end
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
if (now > _debugLogExpiry) {
|
||||||
|
qDebug() << "Malformed AvatarData packet before ChatMessage: minPossibleSize = " << minPossibleSize
|
||||||
|
<< " but maxAvailableSize = " << maxAvailableSize;
|
||||||
|
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||||
|
}
|
||||||
|
return maxAvailableSize;
|
||||||
|
}
|
||||||
|
{ // chat payload
|
||||||
|
_chatMessage = string((char*)sourceBuffer, chatMessageSize);
|
||||||
|
sourceBuffer += chatMessageSize * sizeof(char);
|
||||||
|
} // 1 + chatMessageSize bytes
|
||||||
|
|
||||||
|
{ // bitFlags and face data
|
||||||
|
unsigned char bitItems = 0;
|
||||||
|
bitItems = (unsigned char)*sourceBuffer++;
|
||||||
|
|
||||||
|
// key state, stored as a semi-nibble in the bitItems
|
||||||
|
_keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT);
|
||||||
|
|
||||||
|
// hand state, stored as a semi-nibble in the bitItems
|
||||||
|
_handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT);
|
||||||
|
|
||||||
|
_headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED);
|
||||||
|
_isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED);
|
||||||
|
|
||||||
|
if (_headData->_isFaceshiftConnected) {
|
||||||
|
minPossibleSize += 4 * sizeof(float) + 1; // four floats + one byte for blendDataSize
|
||||||
|
if (minPossibleSize > maxAvailableSize) {
|
||||||
|
// this packet is malformed so we pretend to read to the end
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
if (now > _debugLogExpiry) {
|
||||||
|
qDebug() << "Malformed AvatarData packet after BitItems: minPossibleSize = " << minPossibleSize
|
||||||
|
<< " but maxAvailableSize = " << maxAvailableSize;
|
||||||
|
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||||
|
}
|
||||||
|
return maxAvailableSize;
|
||||||
|
}
|
||||||
|
// unpack face data
|
||||||
|
memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
|
||||||
|
memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
|
||||||
|
memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
|
||||||
|
memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float));
|
||||||
|
sourceBuffer += sizeof(float);
|
||||||
|
|
||||||
|
int numCoefficients = (int)(*sourceBuffer++);
|
||||||
|
int blendDataSize = numCoefficients * sizeof(float);
|
||||||
|
minPossibleSize += blendDataSize;
|
||||||
|
if (minPossibleSize > maxAvailableSize) {
|
||||||
|
// this packet is malformed so we pretend to read to the end
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
if (now > _debugLogExpiry) {
|
||||||
|
qDebug() << "Malformed AvatarData packet after Blendshapes: minPossibleSize = " << minPossibleSize
|
||||||
|
<< " but maxAvailableSize = " << maxAvailableSize;
|
||||||
|
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||||
|
}
|
||||||
|
return maxAvailableSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
_headData->_blendshapeCoefficients.resize(numCoefficients);
|
||||||
|
memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, blendDataSize);
|
||||||
|
sourceBuffer += numCoefficients * sizeof(float);
|
||||||
|
|
||||||
|
//bitItemsDataSize = 4 * sizeof(float) + 1 + blendDataSize;
|
||||||
|
}
|
||||||
|
} // 1 + bitItemsDataSize bytes
|
||||||
|
|
||||||
|
{ // pupil dilation
|
||||||
|
sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f);
|
||||||
|
} // 1 byte
|
||||||
|
|
||||||
// joint data
|
// joint data
|
||||||
int jointCount = *sourceBuffer++;
|
int numJoints = *sourceBuffer++;
|
||||||
_jointData.resize(jointCount);
|
int bytesOfValidity = (int)ceil((float)numJoints / 8.f);
|
||||||
unsigned char validity = 0;
|
minPossibleSize += bytesOfValidity;
|
||||||
int validityBit = 0;
|
if (minPossibleSize > maxAvailableSize) {
|
||||||
for (int i = 0; i < jointCount; i++) {
|
// this packet is malformed so we pretend to read to the end
|
||||||
if (validityBit == 0) {
|
quint64 now = usecTimestampNow();
|
||||||
validity = *sourceBuffer++;
|
if (now > _debugLogExpiry) {
|
||||||
}
|
qDebug() << "Malformed AvatarData packet after JointValidityBits: minPossibleSize = " << minPossibleSize
|
||||||
_jointData[i].valid = (bool)(validity & (1 << validityBit));
|
<< " but maxAvailableSize = " << maxAvailableSize;
|
||||||
validityBit = (validityBit + 1) % BITS_IN_BYTE;
|
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||||
|
}
|
||||||
|
return maxAvailableSize;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < jointCount; i++) {
|
int numValidJoints = 0;
|
||||||
JointData& data = _jointData[i];
|
_jointData.resize(numJoints);
|
||||||
if (data.valid) {
|
{ // validity bits
|
||||||
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation);
|
unsigned char validity = 0;
|
||||||
|
int validityBit = 0;
|
||||||
|
for (int i = 0; i < numJoints; i++) {
|
||||||
|
if (validityBit == 0) {
|
||||||
|
validity = *sourceBuffer++;
|
||||||
|
}
|
||||||
|
bool valid = (bool)(validity & (1 << validityBit));
|
||||||
|
if (valid) {
|
||||||
|
++numValidJoints;
|
||||||
|
}
|
||||||
|
_jointData[i].valid = valid;
|
||||||
|
validityBit = (validityBit + 1) % BITS_IN_BYTE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 1 + bytesOfValidity bytes
|
||||||
|
|
||||||
|
minPossibleSize += numValidJoints * 8; // 8 bytes per quaternion
|
||||||
|
if (minPossibleSize > maxAvailableSize) {
|
||||||
|
// this packet is malformed so we pretend to read to the end
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
if (now > _debugLogExpiry) {
|
||||||
|
qDebug() << "Malformed AvatarData packet after JointData: minPossibleSize = " << minPossibleSize
|
||||||
|
<< " but maxAvailableSize = " << maxAvailableSize;
|
||||||
|
_debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY;
|
||||||
|
}
|
||||||
|
return maxAvailableSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // joint data
|
||||||
|
for (int i = 0; i < numJoints; i++) {
|
||||||
|
JointData& data = _jointData[i];
|
||||||
|
if (data.valid) {
|
||||||
|
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // numJoints * 8 bytes
|
||||||
|
|
||||||
return sourceBuffer - startPosition;
|
return sourceBuffer - startPosition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,10 +33,10 @@ typedef unsigned long long quint64;
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
#include <QtCore/QStringList>
|
#include <QtCore/QStringList>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
#include <QtCore/QUuid>
|
|
||||||
#include <QtCore/QVector>
|
#include <QtCore/QVector>
|
||||||
#include <QtCore/QVariantMap>
|
#include <QtCore/QVariantMap>
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
|
#include <QMap>
|
||||||
|
|
||||||
#include <CollisionInfo.h>
|
#include <CollisionInfo.h>
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
|
@ -256,6 +256,8 @@ protected:
|
||||||
|
|
||||||
static QNetworkAccessManager* networkAccessManager;
|
static QNetworkAccessManager* networkAccessManager;
|
||||||
|
|
||||||
|
quint64 _debugLogExpiry;
|
||||||
|
|
||||||
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
|
||||||
AvatarData(const AvatarData&);
|
AvatarData(const AvatarData&);
|
||||||
|
|
Loading…
Reference in a new issue