Revision control
++ // Copyright (C) 2017 Ford Motor Company
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include <QtCore/qabstractitemmodel.h>
++ #include <QtCore/qbytearrayview.h>
++
++ #include "qremoteobjectcontainers_p.h"
++ #include "qremoteobjectpendingcall.h"
++ #include "qremoteobjectsource.h"
++ #include "qremoteobjectsource_p.h"
++ #include "qremoteobjectpacket_p.h"
++ #include "qconnectionfactories.h"
++ #include "qconnectionfactories_p.h"
++ #include <cstring>
++
++ //#define QTRO_VERBOSE_PROTOCOL
++ QT_BEGIN_NAMESPACE
++
++
++ // Add methods so we can use QMetaEnum in a set
++ // Note for both functions we are skipping string comparisons/hashes. Since the
++ // metaObjects are the same, we can just use the address of the string.
++ inline bool operator==(const QMetaEnum e1, const QMetaEnum e2)
++ {
++ return e1.enclosingMetaObject() == e2.enclosingMetaObject()
++ && e1.name() == e2.name()
++ && e1.enumName() == e2.enumName()
++ && e1.scope() == e2.scope();
++ }
++
++ inline size_t qHash(const QMetaEnum &key, size_t seed=0) Q_DECL_NOTHROW
++ {
++ return qHash(key.enclosingMetaObject(), seed) ^ qHash(static_cast<const void *>(key.name()), seed)
++ ^ qHash(static_cast<const void *>(key.enumName()), seed) ^ qHash(static_cast<const void *>(key.scope()), seed);
++ }
++
++ static bool isSequentialGadgetType(QMetaType metaType)
++ {
++ if (QMetaType::canConvert(metaType, QMetaType::fromType<QMetaSequence::Iterable>())) {
++ static QHash<int, bool> lookup;
++ if (!lookup.contains(metaType.id())) {
++ auto stubVariant = QVariant(metaType, nullptr);
++ auto asIterable = stubVariant.value<QMetaSequence::Iterable>();
++ auto valueMetaType = asIterable.metaContainer().valueMetaType();
++ lookup[metaType.id()] = valueMetaType.flags().testFlag(QMetaType::IsGadget);
++ }
++ return lookup[metaType.id()];
++ }
++ return false;
++ }
++
++ static bool isAssociativeGadgetType(QMetaType metaType)
++ {
++ if (QMetaType::canConvert(metaType, QMetaType::fromType<QMetaAssociation::Iterable>())) {
++ static QHash<int, bool> lookup;
++ if (!lookup.contains(metaType.id())) {
++ auto stubVariant = QVariant(metaType, nullptr);
++ auto asIterable = stubVariant.value<QMetaAssociation::Iterable>();
++ auto valueMetaType = asIterable.metaContainer().mappedMetaType();
++ lookup[metaType.id()] = valueMetaType.flags().testFlag(QMetaType::IsGadget);
++ }
++ return lookup[metaType.id()];
++ }
++ return false;
++ }
++
++ using namespace QtRemoteObjects;
++
++ namespace QRemoteObjectPackets {
++
++ QMetaType transferTypeForEnum(QMetaType enumType)
++ {
++ const auto size = enumType.sizeOf();
++ switch (size) {
++ case 1: return QMetaType::fromType<qint8>();
++ case 2: return QMetaType::fromType<qint16>();
++ case 4: return QMetaType::fromType<qint32>();
++ // Qt currently only supports enum values of 4 or less bytes (QMetaEnum value(index) returns int)
++ // case 8: args.push_back(QVariant(QMetaType::Int, argv[i + 1])); break;
++ default:
++ qCWarning(QT_REMOTEOBJECT_IO) << "Invalid enum detected (Dynamic Replica)" << enumType.name() << "with size" << size;
++ return QMetaType::fromType<qint32>();
++ }
++ }
++
++ // QDataStream sends QVariants of custom types by sending their typename, allowing decode
++ // on the receiving side. For QtRO and enums, this won't work, as the enums have different
++ // scopes. E.g., the examples have ParentClassSource::MyEnum and ParentClassReplica::MyEnum.
++ // Dynamic types will be created as ParentClass::MyEnum. So instead, we change the variants
++ // to integers (encodeVariant) when sending them. On the receive side, the we know the
++ // types of properties and the signatures for methods, so we can use that information to
++ // decode the integer variant into an enum variant (via decodeVariant).
++ QVariant encodeVariant(const QVariant &value)
++ {
++ const auto metaType = value.metaType();
++ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
++ auto converted = QVariant(value);
++ auto transferType = transferTypeForEnum(metaType);
++ converted.convert(transferType);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Converting from enum to integer type" << transferType.sizeOf() << converted << value;
++ #endif
++ return converted;
++ }
++ if (isSequentialGadgetType(metaType)) { // Doesn't include QtROSequentialContainer
++ // TODO Way to create the QVariant without copying the QSQ_?
++ QSQ_ sequence(value);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Encoding sequential container" << metaType.name() << "to QSQ_ to transmit";
++ #endif
++ return QVariant::fromValue<QSQ_>(sequence);
++ }
++ if (metaType == QMetaType::fromType<QtROSequentialContainer>()) {
++ QSQ_ sequence(value);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Encoding QtROSequentialContainer container to QSQ_ to transmit";
++ #endif
++ return QVariant::fromValue<QSQ_>(sequence);
++ }
++ if (isAssociativeGadgetType(metaType)) { // Doesn't include QtROAssociativeContainer
++ QAS_ map(value);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Encoding associative container" << metaType.name() << "to QAS_ to transmit";
++ #endif
++ return QVariant::fromValue<QAS_>(map);
++ }
++ if (metaType == QMetaType::fromType<QtROAssociativeContainer>()) {
++ QAS_ map(value);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Encoding QtROAssociativeContainer container to QAS_ to transmit";
++ #endif
++ return QVariant::fromValue<QAS_>(map);
++ }
++ return value;
++ }
++
++ QVariant decodeVariant(QVariant &&value, QMetaType metaType)
++ {
++ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ QVariant encoded(value);
++ #endif
++ value.convert(metaType);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Converting to enum from integer type" << value << encoded;
++ #endif
++ } else if (value.metaType() == QMetaType::fromType<QRemoteObjectPackets::QSQ_>()) {
++ const auto *qsq_ = static_cast<const QRemoteObjectPackets::QSQ_ *>(value.constData());
++ QDataStream in(qsq_->values);
++ auto containerType = QMetaType::fromName(qsq_->typeName.constData());
++ bool isRegistered = containerType.isRegistered();
++ if (isRegistered) {
++ QVariant seq{containerType, nullptr};
++ if (!seq.canView<QMetaSequence::Iterable>()) {
++ qWarning() << "Unsupported container" << qsq_->typeName.constData()
++ << "(not viewable)";
++ return QVariant();
++ }
++ QMetaSequence::Iterable seqIter = seq.view<QMetaSequence::Iterable>();
++ const QMetaSequence metaSequence = seqIter.metaContainer();
++ if (!metaSequence.canAddValue()) {
++ qWarning() << "Unsupported container" << qsq_->typeName.constData()
++ << "(Unable to add values)";
++ return QVariant();
++ }
++ QByteArray valueTypeName;
++ quint32 count;
++ in >> valueTypeName;
++ in >> count;
++ QMetaType valueType = QMetaType::fromName(valueTypeName.constData());
++ QVariant tmp{valueType, nullptr};
++ for (quint32 i = 0; i < count; i++) {
++ if (!valueType.load(in, tmp.data())) {
++ if (metaSequence.canRemoveValue() || i == 0) {
++ for (quint32 ii = 0; ii < i; ii++)
++ metaSequence.removeValue(seqIter.mutableIterable());
++ qWarning("QSQ_: unable to load type '%s', returning an empty list.", valueTypeName.constData());
++ } else {
++ qWarning("QSQ_: unable to load type '%s', returning a partial list.", valueTypeName.constData());
++ }
++ break;
++ }
++
++ QtPrivate::QVariantTypeCoercer coercer;
++ metaSequence.addValue(
++ seqIter.mutableIterable(),
++ coercer.coerce(tmp, metaSequence.valueMetaType()));
++ }
++ value = seq;
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Decoding QSQ_ to sequential container" << containerType.name()
++ << valueTypeName;
++ #endif
++ } else {
++ QtROSequentialContainer container{};
++ in >> container;
++ container.m_typeName = qsq_->typeName;
++ value = QVariant(QMetaType::fromType<QtROSequentialContainer>(), &container);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Decoding QSQ_ to QtROSequentialContainer of"
++ << container.m_valueTypeName;
++ #endif
++ }
++ } else if (value.metaType() == QMetaType::fromType<QRemoteObjectPackets::QAS_>()) {
++ const auto *qas_ = static_cast<const QRemoteObjectPackets::QAS_ *>(value.constData());
++ QDataStream in(qas_->values);
++ auto containerType = QMetaType::fromName(qas_->typeName.constData());
++ bool isRegistered = containerType.isRegistered();
++ if (isRegistered) {
++ QVariant map{containerType, nullptr};
++ if (!map.canView<QMetaAssociation::Iterable>()) {
++ qWarning() << "Unsupported container" << qas_->typeName.constData()
++ << "(not viewable)";
++ return QVariant();
++ }
++ QMetaAssociation::Iterable mapIter = map.view<QMetaAssociation::Iterable>();
++ if (!mapIter.metaContainer().canSetMappedAtKey()) {
++ qWarning() << "Unsupported container" << qas_->typeName.constData()
++ << "(Unable to insert values)";
++ return QVariant();
++ }
++ QByteArray keyTypeName, valueTypeName;
++ quint32 count;
++ in >> keyTypeName;
++ QMetaType keyType = QMetaType::fromName(keyTypeName.constData());
++ if (!keyType.isValid()) {
++ // This happens for class enums, where the passed keyType is <ClassName>::<enum>
++ // For a compiled replica, the keyType is <ClassName>Replica::<enum>
++ // Since the full typename is registered, we can pull the keyType from there
++ keyType = mapIter.metaContainer().keyMetaType();
++ }
++ QMetaType transferType = keyType;
++ if (keyType.flags().testFlag(QMetaType::IsEnumeration))
++ transferType = transferTypeForEnum(keyType);
++ QVariant key{transferType, nullptr};
++ in >> valueTypeName;
++ QMetaType valueType = QMetaType::fromName(valueTypeName.constData());
++ QVariant val{valueType, nullptr};
++ in >> count;
++ for (quint32 i = 0; i < count; i++) {
++ if (!transferType.load(in, key.data())) {
++ map = QVariant{containerType, nullptr};
++ qWarning("QAS_: unable to load key of type '%s', returning an empty map.",
++ keyTypeName.constData());
++ break;
++ }
++ if (!valueType.load(in, val.data())) {
++ map = QVariant{containerType, nullptr};
++ qWarning("QAS_: unable to load value of type '%s', returning an empty map.",
++ valueTypeName.constData());
++ break;
++ }
++ if (transferType != keyType) {
++ QVariant enumKey(key);
++ enumKey.convert(keyType);
++ mapIter.setValue(enumKey, val);
++ } else {
++ mapIter.setValue(key, val);
++ }
++ }
++ value = map;
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Decoding QAS_ to associative container" << containerType.name()
++ << valueTypeName << keyTypeName << count << mapIter.size() << map;
++ #endif
++ } else {
++ QtROAssociativeContainer container{};
++ in >> container;
++ container.m_typeName = qas_->typeName;
++ value = QVariant(QMetaType::fromType<QtROAssociativeContainer>(), &container);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Decoding QAS_ to QtROAssociativeContainer of"
++ << container.m_valueTypeName;
++ #endif
++ }
++ } else if (metaType.isValid() && metaType != QMetaType::fromType<QVariant>()
++ && value.metaType() != metaType) {
++ if (!value.canConvert(metaType)) {
++ qWarning("The provided type %s does not match the expected type %s and is "
++ "not convertible to it. Returning a default-constructed QVariant "
++ "holding the type %s.",
++ value.metaType().name(), metaType.name(), metaType.name());
++ return QVariant(metaType);
++ }
++ value.convert(metaType);
++ }
++ return std::move(value);
++ }
++
++ void QDataStreamCodec::serializeProperty(const QRemoteObjectSourceBase *source, int internalIndex)
++ {
++ serializeProperty(m_packet, source, internalIndex);
++ }
++
++ void QDataStreamCodec::serializeProperty(QDataStream &ds, const QRemoteObjectSourceBase *source, int internalIndex)
++ {
++ const int propertyIndex = source->m_api->sourcePropertyIndex(internalIndex);
++ Q_ASSERT (propertyIndex >= 0);
++ const auto target = source->m_api->isAdapterProperty(internalIndex) ? source->m_adapter : source->m_object;
++ const auto property = target->metaObject()->property(propertyIndex);
++ const QVariant value = property.read(target);
++ if (property.metaType().flags().testFlag(QMetaType::PointerToQObject)) {
++ auto const childSource = source->m_children.value(internalIndex);
++ auto valueAsPointerToQObject = qvariant_cast<QObject *>(value);
++ if (childSource->m_object != valueAsPointerToQObject)
++ childSource->resetObject(valueAsPointerToQObject);
++ QRO_ qro(childSource);
++ if (source->d->isDynamic && qro.type == ObjectType::CLASS && childSource->m_object && !source->d->sentTypes.contains(qro.typeName)) {
++ QDataStream classDef(&qro.classDefinition, QIODevice::WriteOnly);
++ serializeDefinition(classDef, childSource);
++ source->d->sentTypes.insert(qro.typeName);
++ }
++ ds << QVariant::fromValue<QRO_>(qro);
++ if (qro.isNull)
++ return;
++ const int propertyCount = childSource->m_api->propertyCount();
++ // Put the properties in a buffer, the receiver may not know how to
++ // interpret the types until it registers new ones.
++ QDataStream params(&qro.parameters, QIODevice::WriteOnly);
++ params << propertyCount;
++ for (int internalIndex = 0; internalIndex < propertyCount; ++internalIndex)
++ serializeProperty(params, childSource, internalIndex);
++ ds << qro.parameters;
++ return;
++ }
++ if (source->d->isDynamic && property.userType() == QMetaType::QVariant
++ && value.metaType().flags().testFlag(QMetaType::IsGadget)) {
++ const auto typeName = QString::fromLatin1(value.metaType().name());
++ if (!source->d->sentTypes.contains(typeName)) {
++ QRO_ qro(value);
++ ds << QVariant::fromValue<QRO_>(qro);
++ ds << qro.parameters;
++ source->d->sentTypes.insert(typeName);
++ return;
++ }
++ }
++ ds << encodeVariant(value);
++ }
++
++ void QDataStreamCodec::serializeHandshakePacket()
++ {
++ m_packet.setId(Handshake);
++ m_packet << QString(protocolVersion);
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::serializeInitPacket(const QRemoteObjectRootSource *source)
++ {
++ m_packet.setId(InitPacket);
++ m_packet << source->name();
++ serializeProperties(source);
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::serializeProperties(const QRemoteObjectSourceBase *source)
++ {
++ const SourceApiMap *api = source->m_api;
++
++ //Now copy the property data
++ const int numProperties = api->propertyCount();
++ m_packet << quint32(numProperties); //Number of properties
++
++ for (int internalIndex = 0; internalIndex < numProperties; ++internalIndex)
++ serializeProperty(source, internalIndex);
++ }
++
++ bool deserializeQVariantList(QDataStream &s, QVariantList &l)
++ {
++ // note: optimized version of: QDataStream operator>>(QDataStream& s, QList<T>& l)
++ quint32 c;
++ s >> c;
++
++ const qsizetype count = static_cast<qsizetype>(c);
++ const qsizetype listSize = l.size();
++ if (listSize < count)
++ l.reserve(count);
++ else if (listSize > count)
++ l.resize(count);
++
++ for (int i = 0; i < l.size(); ++i)
++ {
++ if (s.atEnd())
++ return false;
++ s >> l[i];
++ }
++ for (auto i = l.size(); i < count; ++i)
++ {
++ if (s.atEnd())
++ return false;
++ s >> l.emplace_back();
++ }
++ return true;
++ }
++
++ void QDataStreamCodec::deserializeInitPacket(QDataStream &in, QVariantList &values)
++ {
++ const bool success = deserializeQVariantList(in, values);
++ Q_ASSERT(success);
++ Q_UNUSED(success)
++ }
++
++ void QDataStreamCodec::serializeInitDynamicPacket(const QRemoteObjectRootSource *source)
++ {
++ m_packet.setId(InitDynamicPacket);
++ m_packet << source->name();
++ serializeDefinition(m_packet, source);
++ serializeProperties(source);
++ m_packet.finishPacket();
++ }
++
++ static ObjectType getObjectType(const QString &typeName)
++ {
++ if (typeName == QLatin1String("QAbstractItemModelAdapter"))
++ return ObjectType::MODEL;
++ auto tid = QMetaType::fromName(typeName.toUtf8()).id();
++ if (tid == QMetaType::UnknownType)
++ return ObjectType::CLASS;
++ QMetaType type(tid);
++ auto mo = type.metaObject();
++ if (mo && mo->inherits(&QAbstractItemModel::staticMetaObject))
++ return ObjectType::MODEL;
++ return ObjectType::CLASS;
++ }
++
++ static QByteArrayView resolveEnumName(QMetaType t, bool &isFlag)
++ {
++ // Takes types like `MyPOD::Position` or `QFlags<MyPOD::Position>` and returns 'Position`
++ QByteArrayView enumName(t.name());
++ isFlag = enumName.startsWith("QFlags<");
++ auto lastColon = enumName.lastIndexOf(':');
++ if (lastColon >= 0)
++ enumName = QByteArrayView(t.name() + lastColon + 1);
++ if (isFlag)
++ enumName.chop(1);
++ return enumName;
++ }
++
++ static QMetaEnum metaEnumFromType(QMetaType t)
++ {
++ if (t.flags().testFlag(QMetaType::IsEnumeration)) {
++ if (const QMetaObject *m = t.metaObject()) {
++ bool isFlag;
++ auto enumName = resolveEnumName(t, isFlag);
++ if (isFlag) {
++ for (int i = m->enumeratorOffset(); i < m->enumeratorCount(); i++) {
++ auto testType = m->enumerator(i);
++ if (testType.isFlag() &&
++ enumName.compare(QByteArrayView(testType.enumName())) == 0)
++ return testType;
++ }
++ }
++ return m->enumerator(m->indexOfEnumerator(enumName.data()));
++ }
++ }
++ return QMetaEnum();
++ }
++
++ static bool checkEnum(QMetaType metaType, QSet<QMetaEnum> &enums)
++ {
++ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
++ QMetaEnum meta = metaEnumFromType(metaType);
++ enums.insert(meta);
++ return true;
++ }
++ return false;
++ }
++
++ static void recurseMetaobject(const QMetaObject *mo, QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums)
++ {
++ if (!mo || gadgets.contains(mo))
++ return;
++ gadgets.insert(mo);
++ const int numProperties = mo->propertyCount();
++ for (int i = 0; i < numProperties; ++i) {
++ const auto property = mo->property(i);
++ if (checkEnum(property.metaType(), enums))
++ continue;
++ if (property.metaType().flags().testFlag(QMetaType::IsGadget))
++ recurseMetaobject(property.metaType().metaObject(), gadgets, enums);
++ }
++ }
++
++ // A Source may only use a subset of the metaobjects properties/signals/slots, so we only search
++ // the ones in the API. For nested pointer types, we will have another api to limit the search.
++ // For nested PODs/enums, we search the entire qobject (using the recurseMetaobject call()).
++ void recurseForGadgets(QSet<const QMetaObject *> &gadgets, QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source)
++ {
++ const SourceApiMap *api = source->m_api;
++
++ const int numSignals = api->signalCount();
++ const int numMethods = api->methodCount();
++ const int numProperties = api->propertyCount();
++
++ for (int si = 0; si < numSignals; ++si) {
++ const int params = api->signalParameterCount(si);
++ for (int pi = 0; pi < params; ++pi) {
++ const int type = api->signalParameterType(si, pi);
++ const auto metaType = QMetaType(type);
++ if (checkEnum(metaType, enums))
++ continue;
++ if (!metaType.flags().testFlag(QMetaType::IsGadget))
++ continue;
++ const auto mo = metaType.metaObject();
++ if (source->d->sentTypes.contains(QLatin1String(mo->className())))
++ continue;
++ recurseMetaobject(mo, gadgets, enums);
++ source->d->sentTypes.insert(QLatin1String(mo->className()));
++ }
++ }
++
++ for (int mi = 0; mi < numMethods; ++mi) {
++ const int params = api->methodParameterCount(mi);
++ for (int pi = 0; pi < params; ++pi) {
++ const int type = api->methodParameterType(mi, pi);
++ const auto metaType = QMetaType(type);
++ if (checkEnum(metaType, enums))
++ continue;
++ if (!metaType.flags().testFlag(QMetaType::IsGadget))
++ continue;
++ const auto mo = metaType.metaObject();
++ if (source->d->sentTypes.contains(QLatin1String(mo->className())))
++ continue;
++ recurseMetaobject(mo, gadgets, enums);
++ source->d->sentTypes.insert(QLatin1String(mo->className()));
++ }
++ }
++ for (int pi = 0; pi < numProperties; ++pi) {
++ const int index = api->sourcePropertyIndex(pi);
++ Q_ASSERT(index >= 0);
++ const auto target = api->isAdapterProperty(pi) ? source->m_adapter : source->m_object;
++ const auto metaProperty = target->metaObject()->property(index);
++ const auto metaType = metaProperty.metaType();
++ if (checkEnum(metaType, enums))
++ continue;
++ if (metaType.flags().testFlag(QMetaType::PointerToQObject)) {
++ auto const objectType = getObjectType(QString::fromLatin1(metaProperty.typeName()));
++ if (objectType == ObjectType::CLASS) {
++ auto const childSource = source->m_children.value(pi);
++ if (childSource->m_object)
++ recurseForGadgets(gadgets, enums, childSource);
++ }
++ }
++ if (!metaType.flags().testFlag(QMetaType::IsGadget))
++ continue;
++ const auto mo = metaType.metaObject();
++ if (source->d->sentTypes.contains(QLatin1String(mo->className())))
++ continue;
++ recurseMetaobject(mo, gadgets, enums);
++ source->d->sentTypes.insert(QLatin1String(mo->className()));
++ }
++ }
++
++ static bool checkForEnumsInSource(const QMetaObject *meta, const QRemoteObjectSourceBase *source)
++ {
++ if (source->m_object->inherits(meta->className()))
++ return true;
++ for (const auto &child : source->m_children) {
++ if (child->m_object && checkForEnumsInSource(meta, child))
++ return true;
++ }
++ return false;
++ }
++
++ static void serializeEnum(QDataStream &ds, const QMetaEnum &enumerator)
++ {
++ ds << QByteArray::fromRawData(enumerator.name(), qsizetype(qstrlen(enumerator.name())));
++ ds << enumerator.isFlag();
++ ds << enumerator.isScoped();
++ const auto typeName = QByteArray(enumerator.scope()).append("::").append(enumerator.name());
++ const quint32 size = quint32(QMetaType::fromName(typeName.constData()).sizeOf());
++ ds << size;
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug(" Enum (name = %s, size = %d, isFlag = %s, isScoped = %s):", enumerator.name(), size, enumerator.isFlag() ? "true" : "false", enumerator.isScoped() ? "true" : "false");
++ #endif
++ const int keyCount = enumerator.keyCount();
++ ds << keyCount;
++ for (int k = 0; k < keyCount; ++k) {
++ ds << QByteArray::fromRawData(enumerator.key(k), qsizetype(qstrlen(enumerator.key(k))));
++ ds << enumerator.value(k);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug(" Key %d (name = %s, value = %d):", k, enumerator.key(k), enumerator.value(k));
++ #endif
++ }
++ }
++
++ static void serializeGadgets(QDataStream &ds, const QSet<const QMetaObject *> &gadgets, const QSet<QMetaEnum> &enums, const QRemoteObjectSourceBase *source=nullptr)
++ {
++ // Determine how to handle the enums found
++ QSet<QMetaEnum> qtEnums;
++ QSet<const QMetaObject *> dynamicEnumMetaObjects;
++ for (const auto &metaEnum : enums) {
++ auto const metaObject = metaEnum.enclosingMetaObject();
++ if (gadgets.contains(metaObject)) // Part of a gadget will we serialize
++ continue;
++ // This checks if the enum is defined in our object heirarchy, in which case it will
++ // already have been serialized.
++ if (source && checkForEnumsInSource(metaObject, source->d->root))
++ continue;
++ // qtEnums are enumerations already known by Qt, so we only need register them.
++ // We don't need to send all of the key/value data.
++ if (metaObject == &Qt::staticMetaObject) // Are the other Qt metaclasses for enums?
++ qtEnums.insert(metaEnum);
++ else
++ dynamicEnumMetaObjects.insert(metaEnum.enclosingMetaObject());
++ }
++ ds << quint32(qtEnums.size());
++ for (const auto &metaEnum : std::as_const(qtEnums)) {
++ QByteArray enumName(metaEnum.scope());
++ enumName.append("::", 2).append(metaEnum.name());
++ ds << enumName;
++ }
++ const auto allMetaObjects = gadgets + dynamicEnumMetaObjects;
++ ds << quint32(allMetaObjects.size());
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Found" << gadgets.size() << "gadget/pod and" << (allMetaObjects.size() - gadgets.size()) << "enum types";
++ int i = 0;
++ #endif
++ // There isn't an easy way to update a metaobject incrementally, so we
++ // send all of the metaobject's enums, but no properties, when an external
++ // enum is requested.
++ for (auto const meta : allMetaObjects) {
++ ds << QByteArray::fromRawData(meta->className(), qsizetype(qstrlen(meta->className())));
++ int propertyCount = gadgets.contains(meta) ? meta->propertyCount() : 0;
++ ds << quint32(propertyCount);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug(" Gadget %d (name = %s, # properties = %d, # enums = %d):", i++, meta->className(), propertyCount, meta->enumeratorCount() - meta->enumeratorOffset());
++ #endif
++ for (int j = 0; j < propertyCount; j++) {
++ auto prop = meta->property(j);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug(" Data member %d (name = %s, type = %s):", j, prop.name(), prop.typeName());
++ #endif
++ ds << QByteArray::fromRawData(prop.name(), qsizetype(qstrlen(prop.name())));
++ ds << QByteArray::fromRawData(prop.typeName(), qsizetype(qstrlen(prop.typeName())));
++ }
++ int enumCount = meta->enumeratorCount() - meta->enumeratorOffset();
++ ds << quint32(enumCount);
++ for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) {
++ auto const enumMeta = meta->enumerator(j);
++ serializeEnum(ds, enumMeta);
++ }
++ }
++ }
++
++ void QDataStreamCodec::serializeDefinition(QDataStream &ds, const QRemoteObjectSourceBase *source)
++ {
++ const SourceApiMap *api = source->m_api;
++ const QByteArray desiredClassName(api->typeName().toLatin1());
++ const QByteArray originalClassName = api->className();
++ // The dynamic class will be called typeName on the receiving side of this definition
++ // However, there are types like enums that have the QObject's class name. Replace()
++ // will convert a parameter such as "ParentClassSource::MyEnum" to "ParentClass::MyEnum"
++ // so the type can be properly resolved and registered.
++ auto replace = [&originalClassName, &desiredClassName](QByteArray &name) {
++ name.replace(originalClassName, desiredClassName);
++ };
++
++ ds << source->m_api->typeName();
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Serializing definition for" << source->m_api->typeName();
++ #endif
++
++ //Now copy the property data
++ const int numEnums = api->enumCount();
++ const auto metaObject = source->m_object->metaObject();
++ ds << quint32(numEnums); //Number of Enums
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Found" << numEnums << "enumeration types";
++ #endif
++ for (int i = 0; i < numEnums; ++i) {
++ auto enumerator = metaObject->enumerator(api->sourceEnumIndex(i));
++ Q_ASSERT(enumerator.isValid());
++ serializeEnum(ds, enumerator);
++ }
++
++ QSet<const QMetaObject *> gadgets;
++ QSet<QMetaEnum> enums;
++ recurseForGadgets(gadgets, enums, source);
++ serializeGadgets(ds, gadgets, enums, source);
++
++ const int numSignals = api->signalCount();
++ ds << quint32(numSignals); //Number of signals
++ for (int i = 0; i < numSignals; ++i) {
++ const int index = api->sourceSignalIndex(i);
++ Q_ASSERT(index >= 0);
++ auto signature = api->signalSignature(i);
++ replace(signature);
++ const int count = api->signalParameterCount(i);
++ for (int pi = 0; pi < count; ++pi) {
++ const auto metaType = QMetaType(api->signalParameterType(i, pi));
++ if (isSequentialGadgetType(metaType))
++ signature.replace(metaType.name(), "QtROSequentialContainer");
++ else if (isAssociativeGadgetType(metaType))
++ signature.replace(metaType.name(), "QtROAssociativeContainer");
++ }
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Signal" << i << "(signature =" << signature << "parameter names =" << api->signalParameterNames(i) << ")";
++ #endif
++ ds << signature;
++ ds << api->signalParameterNames(i);
++ }
++
++ const int numMethods = api->methodCount();
++ ds << quint32(numMethods); //Number of methods
++ for (int i = 0; i < numMethods; ++i) {
++ const int index = api->sourceMethodIndex(i);
++ Q_ASSERT(index >= 0);
++ auto signature = api->methodSignature(i);
++ replace(signature);
++ const int count = api->methodParameterCount(i);
++ for (int pi = 0; pi < count; ++pi) {
++ const auto metaType = QMetaType(api->methodParameterType(i, pi));
++ if (isSequentialGadgetType(metaType))
++ signature.replace(metaType.name(), "QtROSequentialContainer");
++ else if (isAssociativeGadgetType(metaType))
++ signature.replace(metaType.name(), "QtROAssociativeContainer");
++ }
++ auto typeName = api->typeName(i);
++ replace(typeName);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Slot" << i << "(signature =" << signature << "parameter names =" << api->methodParameterNames(i) << "return type =" << typeName << ")";
++ #endif
++ ds << signature;
++ ds << typeName;
++ ds << api->methodParameterNames(i);
++ }
++
++ const int numProperties = api->propertyCount();
++ ds << quint32(numProperties); //Number of properties
++ for (int i = 0; i < numProperties; ++i) {
++ const int index = api->sourcePropertyIndex(i);
++ Q_ASSERT(index >= 0);
++
++ const auto target = api->isAdapterProperty(i) ? source->m_adapter : source->m_object;
++ const auto metaProperty = target->metaObject()->property(index);
++ ds << metaProperty.name();
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Property" << i << "name =" << metaProperty.name();
++ #endif
++ if (metaProperty.metaType().flags().testFlag(QMetaType::PointerToQObject)) {
++ auto objectType = getObjectType(QLatin1String(metaProperty.typeName()));
++ ds << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Type:" << (objectType == ObjectType::CLASS ? "QObject*" : "QAbstractItemModel*");
++ #endif
++ } else {
++ if (isSequentialGadgetType(metaProperty.metaType())) {
++ ds << "QtROSequentialContainer";
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Type:" << "QtROSequentialContainer";
++ #endif
++ } else if (isAssociativeGadgetType(metaProperty.metaType())) {
++ ds << "QtROAssociativeContainer";
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Type:" << "QtROAssociativeContainer";
++ #endif
++ } else {
++ ds << metaProperty.typeName();
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Type:" << metaProperty.typeName();
++ #endif
++ }
++ }
++ if (metaProperty.notifySignalIndex() == -1) {
++ ds << QByteArray();
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Notification signal: None";
++ #endif
++ } else {
++ auto signature = metaProperty.notifySignal().methodSignature();
++ replace(signature);
++ ds << signature;
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Notification signal:" << signature;
++ #endif
++ }
++ }
++ }
++
++ void QDataStreamCodec::serializeAddObjectPacket(const QString &name, bool isDynamic)
++ {
++ m_packet.setId(AddObject);
++ m_packet << name;
++ m_packet << isDynamic;
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::deserializeAddObjectPacket(QDataStream &ds, bool &isDynamic)
++ {
++ ds >> isDynamic;
++ }
++
++ void QDataStreamCodec::serializeRemoveObjectPacket(const QString &name)
++ {
++ m_packet.setId(RemoveObject);
++ m_packet << name;
++ m_packet.finishPacket();
++ }
++ //There is no deserializeRemoveObjectPacket - no parameters other than id and name
++
++ void QDataStreamCodec::serializeInvokePacket(const QString &name, int call, int index, const QVariantList &args, int serialId, int propertyIndex)
++ {
++ m_packet.setId(InvokePacket);
++ m_packet << name;
++ m_packet << call;
++ m_packet << index;
++
++ m_packet << quint32(args.size());
++ for (const auto &arg : args)
++ m_packet << encodeVariant(arg);
++
++ m_packet << serialId;
++ m_packet << propertyIndex;
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::deserializeInvokePacket(QDataStream& in, int &call, int &index, QVariantList &args, int &serialId, int &propertyIndex)
++ {
++ in >> call;
++ in >> index;
++ const bool success = deserializeQVariantList(in, args);
++ Q_ASSERT(success);
++ Q_UNUSED(success)
++ in >> serialId;
++ in >> propertyIndex;
++ }
++
++ void QDataStreamCodec::serializeInvokeReplyPacket(const QString &name, int ackedSerialId, const QVariant &value)
++ {
++ m_packet.setId(InvokeReplyPacket);
++ m_packet << name;
++ m_packet << ackedSerialId;
++ m_packet << value;
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::deserializeInvokeReplyPacket(QDataStream& in, int &ackedSerialId, QVariant &value){
++ in >> ackedSerialId;
++ in >> value;
++ }
++
++ void QDataStreamCodec::serializePropertyChangePacket(QRemoteObjectSourceBase *source, int signalIndex)
++ {
++ int internalIndex = source->m_api->propertyRawIndexFromSignal(signalIndex);
++ m_packet.setId(PropertyChangePacket);
++ m_packet << source->name();
++ m_packet << internalIndex;
++ serializeProperty(source, internalIndex);
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::deserializePropertyChangePacket(QDataStream& in, int &index, QVariant &value)
++ {
++ in >> index;
++ in >> value;
++ }
++
++ void QDataStreamCodec::serializeObjectListPacket(const ObjectInfoList &objects)
++ {
++ m_packet.setId(ObjectList);
++ m_packet << objects;
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::deserializeObjectListPacket(QDataStream &in, ObjectInfoList &objects)
++ {
++ in >> objects;
++ }
++
++ void QDataStreamCodec::serializePingPacket(const QString &name)
++ {
++ m_packet.setId(Ping);
++ m_packet << name;
++ m_packet.finishPacket();
++ }
++
++ void QDataStreamCodec::serializePongPacket(const QString &name)
++ {
++ m_packet.setId(Pong);
++ m_packet << name;
++ m_packet.finishPacket();
++ }
++
++ QRO_::QRO_(QRemoteObjectSourceBase *source)
++ : name(source->name())
++ , typeName(source->m_api->typeName())
++ , type(source->m_adapter ? ObjectType::MODEL : getObjectType(typeName))
++ , isNull(source->m_object == nullptr)
++ , classDefinition()
++ , parameters()
++ {}
++
++ QRO_::QRO_(const QVariant &value)
++ : type(ObjectType::GADGET)
++ , isNull(false)
++ {
++ const auto metaType = value.metaType();
++ auto meta = metaType.metaObject();
++ QDataStream out(&classDefinition, QIODevice::WriteOnly);
++ const int numProperties = meta->propertyCount();
++ const auto name = metaType.name();
++ const auto typeName = QByteArray::fromRawData(name, qsizetype(qstrlen(name)));
++ out << quint32(0) << quint32(1);
++ out << typeName;
++ out << numProperties;
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug("Serializing POD definition to QRO_ (name = %s)", typeName.constData());
++ #endif
++ for (int i = 0; i < numProperties; ++i) {
++ const auto property = meta->property(i);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug(" Data member %d (name = %s, type = %s):", i, property.name(), property.typeName());
++ #endif
++ out << QByteArray::fromRawData(property.name(), qsizetype(qstrlen(property.name())));
++ out << QByteArray::fromRawData(property.typeName(), qsizetype(qstrlen(property.typeName())));
++ }
++ int enumCount = meta->enumeratorCount() - meta->enumeratorOffset();
++ out << quint32(enumCount);
++ for (int j = meta->enumeratorOffset(); j < meta->enumeratorCount(); j++) {
++ auto const enumMeta = meta->enumerator(j);
++ serializeEnum(out, enumMeta);
++ }
++ QDataStream ds(¶meters, QIODevice::WriteOnly);
++ ds << value;
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << " Value:" << value;
++ #endif
++ }
++
++ QDataStream &operator<<(QDataStream &stream, const QRO_ &info)
++ {
++ stream << info.name << info.typeName << quint8(info.type) << info.classDefinition << info.isNull;
++ qCDebug(QT_REMOTEOBJECT) << "Serializing " << info;
++ // info.parameters will be filled in by serializeProperty
++ return stream;
++ }
++
++ QDataStream &operator>>(QDataStream &stream, QRO_ &info)
++ {
++ quint8 tmpType;
++ stream >> info.name >> info.typeName >> tmpType >> info.classDefinition >> info.isNull;
++ info.type = static_cast<ObjectType>(tmpType);
++ qCDebug(QT_REMOTEOBJECT) << "Deserializing " << info;
++ if (!info.isNull)
++ stream >> info.parameters;
++ return stream;
++ }
++
++ QSQ_::QSQ_(const QVariant &variant)
++ {
++ QMetaSequence::Iterable sequence;
++ QMetaType valueType;
++ if (variant.metaType() == QMetaType::fromType<QtROSequentialContainer>()) {
++ auto container = static_cast<const QtROSequentialContainer *>(variant.constData());
++ typeName = container->m_typeName;
++ valueType = container->m_valueType;
++ valueTypeName = container->m_valueTypeName;
++ sequence = QMetaSequence::Iterable(reinterpret_cast<const QVariantList *>(variant.constData()));
++ } else {
++ sequence = variant.value<QMetaSequence::Iterable>();
++ typeName = QByteArray(variant.metaType().name());
++ valueType = sequence.metaContainer().valueMetaType();
++ valueTypeName = QByteArray(valueType.name());
++ }
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug("Serializing POD sequence to QSQ_ (type = %s, valueType = %s) with size = %lld",
++ typeName.constData(), valueTypeName.constData(), sequence.size());
++ #endif
++ QDataStream ds(&values, QIODevice::WriteOnly);
++ ds << valueTypeName;
++ auto pos = ds.device()->pos();
++ ds << quint32(sequence.size());
++ for (const auto &v : sequence) {
++ if (!valueType.save(ds, v.data())) {
++ ds.device()->seek(pos);
++ ds.resetStatus();
++ ds << quint32(0);
++ values.resize(ds.device()->pos());
++ qWarning("QSQ_: unable to save type '%s', sending empty list.", valueType.name());
++ break;
++ }
++ }
++ }
++
++ QDataStream &operator<<(QDataStream &stream, const QSQ_ &sequence)
++ {
++ stream << sequence.typeName << sequence.valueTypeName << sequence.values;
++ qCDebug(QT_REMOTEOBJECT) << "Serializing " << sequence;
++ return stream;
++ }
++
++ QDataStream &operator>>(QDataStream &stream, QSQ_ &sequence)
++ {
++ stream >> sequence.typeName >> sequence.valueTypeName >> sequence.values;
++ qCDebug(QT_REMOTEOBJECT) << "Deserializing " << sequence;
++ return stream;
++ }
++
++ QAS_::QAS_(const QVariant &variant)
++ {
++ QMetaAssociation::Iterable map;
++ QMetaType keyType, transferType, valueType;
++ const QtROAssociativeContainer *container = nullptr;
++ if (variant.metaType() == QMetaType::fromType<QtROAssociativeContainer>()) {
++ container = static_cast<const QtROAssociativeContainer *>(variant.constData());
++ typeName = container->m_typeName;
++ keyType = container->m_keyType;
++ keyTypeName = container->m_keyTypeName;
++ valueType = container->m_valueType;
++ valueTypeName = container->m_valueTypeName;
++ map = QMetaAssociation::Iterable(reinterpret_cast<const QVariantMap *>(variant.constData()));
++ } else {
++ map = variant.value<QMetaAssociation::Iterable>();
++ typeName = QByteArray(variant.metaType().name());
++ keyType = map.metaContainer().keyMetaType();
++ keyTypeName = QByteArray(keyType.name());
++ valueType = map.metaContainer().mappedMetaType();
++ valueTypeName = QByteArray(valueType.name());
++ }
++ // Special handling for enums...
++ transferType = keyType;
++ if (keyType.flags().testFlag(QMetaType::IsEnumeration)) {
++ transferType = transferTypeForEnum(keyType);
++ auto meta = keyType.metaObject();
++ // If we are the source, make sure the typeName is converted for any downstream replicas
++ // the `meta` variable will be non-null when the enum is a class enum
++ if (meta && !container) {
++ const int ind = meta->indexOfClassInfo(QCLASSINFO_REMOTEOBJECT_TYPE);
++ if (ind >= 0) {
++ bool isFlag = keyTypeName.startsWith("QFlags<");
++ if (isFlag || keyTypeName.startsWith(meta->className())) {
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ QByteArray orig(keyTypeName);
++ #endif
++ if (isFlag) {
++ // Q_DECLARE_FLAGS(Flags, Enum) -> `typedef QFlags<Enum> Flags;`
++ // We know we have an enum for `Flags` because we sent the enums
++ // from the source, so we just need to look up the alias.
++ keyTypeName = keyTypeName.mid(7);
++ keyTypeName.chop(1); // Remove trailing '>'
++ int index = keyTypeName.lastIndexOf(':');
++ for (int i = meta->enumeratorOffset(); i < meta->enumeratorCount(); i++) {
++ auto en = meta->enumerator(i);
++ auto name = keyTypeName.data() + index + 1;
++ if (en.isFlag() && qstrcmp(en.enumName(), name) == 0)
++ keyTypeName.replace(index + 1, qstrlen(en.enumName()), en.name());
++ }
++ }
++ keyTypeName.replace(meta->className(), meta->classInfo(ind).value());
++ QByteArray repName(meta->classInfo(ind).value());
++ repName.append("Replica");
++ typeName.replace(meta->className(), repName);
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug() << "Converted map key typename from" << orig << "to" << keyTypeName;
++ #endif
++ }
++ }
++ }
++ }
++
++ #ifdef QTRO_VERBOSE_PROTOCOL
++ qDebug("Serializing POD map to QAS_ (type = %s, keyType = %s, valueType = %s), size = %lld",
++ typeName.constData(), keyTypeName.constData(), valueTypeName.constData(),
++ map.size());
++ #endif
++ QDataStream ds(&values, QIODevice::WriteOnly);
++ ds << keyTypeName;
++ ds << valueTypeName;
++ auto pos = ds.device()->pos();
++ ds << quint32(map.size());
++ QMetaAssociation::Iterable::const_iterator iter = map.begin();
++ for (int i = 0; i < map.size(); i++) {
++ QVariant key(container ? container->m_keys.at(i) : iter.key());
++ if (transferType != keyType)
++ key.convert(transferType);
++ if (!transferType.save(ds, key.data())) {
++ ds.device()->seek(pos);
++ ds.resetStatus();
++ ds << quint32(0);
++ values.resize(ds.device()->pos());
++ qWarning("QAS_: unable to save key '%s', sending empty map.", keyType.name());
++ break;
++ }
++ if (!valueType.save(ds, iter.value().data())) {
++ ds.device()->seek(pos);
++ ds.resetStatus();
++ ds << quint32(0);
++ values.resize(ds.device()->pos());
++ qWarning("QAS_: unable to save value '%s', sending empty map.", valueType.name());
++ break;
++ }
++ iter++;
++ }
++ }
++
++ QDataStream &operator<<(QDataStream &stream, const QAS_ &map)
++ {
++ stream << map.typeName << map.keyTypeName << map.valueTypeName << map.values;
++ qCDebug(QT_REMOTEOBJECT) << "Serializing " << map;
++ return stream;
++ }
++
++ QDataStream &operator>>(QDataStream &stream, QAS_ &map)
++ {
++ stream >> map.typeName >> map.keyTypeName >> map.valueTypeName >> map.values;
++ qCDebug(QT_REMOTEOBJECT) << "Deserializing " << map;
++ return stream;
++ }
++
++ DataStreamPacket::DataStreamPacket(quint16 id)
++ : QDataStream(&array, QIODevice::WriteOnly), baseAddress(0), size(0)
++ {
++ this->setVersion(QtRemoteObjects::dataStreamVersion);
++ this->setByteOrder(QDataStream::LittleEndian);
++ *this << quint32(0);
++ *this << id;
++ }
++
++ void CodecBase::send(const QSet<QtROIoDeviceBase *> &connections)
++ {
++ const auto bytearray = getPayload();
++ for (auto conn : connections)
++ conn->write(bytearray);
++ reset();
++ }
++
++ void CodecBase::send(const QList<QtROIoDeviceBase *> &connections)
++ {
++ const auto bytearray = getPayload();
++ for (auto conn : connections)
++ conn->write(bytearray);
++ reset();
++ }
++
++ void CodecBase::send(QtROIoDeviceBase *connection)
++ {
++ const auto bytearray = getPayload();
++ connection->write(bytearray);
++ reset();
++ }
++
++ } // namespace QRemoteObjectPackets
++
++ QT_IMPL_METATYPE_EXTERN_TAGGED(QRemoteObjectPackets::QRO_, QRemoteObjectPackets__QRO_)
++
++ QT_END_NAMESPACE