diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp index 8c22ed0b18ebbd..e77cb4e3f76295 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp @@ -29,7 +29,8 @@ namespace core { CastingPlayer * CastingPlayer::mTargetCastingPlayer = nullptr; -void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, unsigned long long int commissioningWindowTimeoutSec) +void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, unsigned long long int commissioningWindowTimeoutSec, + EndpointFilter * desiredEndpointFilter) { ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection called"); @@ -48,40 +49,48 @@ void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, uns mCommissioningWindowTimeoutSec = commissioningWindowTimeoutSec; mTargetCastingPlayer = this; - // If this CastingPlayer is the cache of CastingPlayers the app previously connected to (and has nodeId and fabricIndex of), - // simply Find or Re-establish the CASE session and return early + // If *this* CastingPlayer was previously connected to, its nodeId, fabricIndex and other attributes should be present + // in the CastingStore cache. If that is the case, AND, the cached data contains the endpoint desired by the client, if any, + // as per desiredEndpointFilter, simply Find or Re-establish the CASE session and return early if (cachedCastingPlayers.size() != 0) { it = std::find_if(cachedCastingPlayers.begin(), cachedCastingPlayers.end(), [this](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == *this; }); + // found the CastingPlayer in cache if (it != cachedCastingPlayers.end()) { unsigned index = (unsigned int) std::distance(cachedCastingPlayers.begin(), it); - *this = cachedCastingPlayers[index]; - - FindOrEstablishSession( - nullptr, - [](void * context, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) { - ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer successful"); - CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_CONNECTED; - support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); - VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); - CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); - }, - [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { - ChipLogError(AppServer, "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer failed"); - CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; - support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); - VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); - CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(error, nullptr); - mTargetCastingPlayer = nullptr; - }); - return; // FindOrEstablishSession called. Return early. + if (desiredEndpointFilter == nullptr || ContainsDesiredEndpoint(&cachedCastingPlayers[index], desiredEndpointFilter)) + { + *this = cachedCastingPlayers[index]; + + FindOrEstablishSession( + nullptr, + [](void * context, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) { + ChipLogProgress(AppServer, + "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer successful"); + CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_CONNECTED; + support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, + CastingPlayer::GetTargetCastingPlayer()); + }, + [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { + ChipLogError(AppServer, "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer failed"); + CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(error, nullptr); + mTargetCastingPlayer = nullptr; + }); + return; // FindOrEstablishSession called. Return early. + } } } - // this CastingPlayer is not in the list of cached CastingPlayers previously connected to. This VerifyOrEstablishConnection call + // this CastingPlayer is not in the list of cached CastingPlayers previously connected to or the cached data + // does not contain the endpoint the client desires to interact with. So, this VerifyOrEstablishConnection call // will require User Directed Commissioning. if (chip::Server::GetInstance().GetFailSafeContext().IsFailSafeArmed()) { @@ -167,6 +176,24 @@ void CastingPlayer::FindOrEstablishSession(void * clientContext, chip::OnDeviceC connectionContext->mOnConnectionFailureCallback); } +bool CastingPlayer::ContainsDesiredEndpoint(core::CastingPlayer * cachedCastingPlayer, EndpointFilter * desiredEndpointFilter) +{ + std::vector> cachedEndpoints = cachedCastingPlayer->GetEndpoints(); + for (const auto & cachedEndpoint : cachedEndpoints) + { + bool match = true; + match = match && (desiredEndpointFilter->vendorId == 0 || cachedEndpoint->GetVendorId() == desiredEndpointFilter->vendorId); + match = + match && (desiredEndpointFilter->productId == 0 || cachedEndpoint->GetProductId() == desiredEndpointFilter->productId); + // TODO: check deviceTypeList and clusterList as well + if (match) + { + return true; + } + } + return false; +} + void CastingPlayer::LogDetail() const { if (strlen(mAttributes.id) != 0) diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h index 9d105506640b80..a1ca436eccd8f6 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -21,6 +21,7 @@ #include "Endpoint.h" #include "Types.h" #include "support/ChipDeviceEventHandler.h" +#include "support/EndpointListLoader.h" #include "lib/support/logging/CHIPLogging.h" #include @@ -37,6 +38,13 @@ const int kPortMaxLength = 5; // port is uint16_t const int kIdMaxLength = chip::Dnssd::kHostNameMaxLength + kPortMaxLength + 1; const unsigned long long int kCommissioningWindowTimeoutSec = 3 * 60; // 3 minutes +struct EndpointFilter +{ + uint16_t vendorId = 0; + uint16_t productId = 0; + std::vector deviceTypeList; +}; + class CastingPlayerAttributes { public: @@ -56,6 +64,8 @@ class CastingPlayerAttributes chip::FabricIndex fabricIndex = 0; }; +class Endpoint; + /** * @brief Represents CastingPlayer ConnectionState. * @@ -110,7 +120,8 @@ class CastingPlayer : public std::enable_shared_from_this * Defaults to kCommissioningWindowTimeoutSec. */ void VerifyOrEstablishConnection(ConnectCallback onCompleted, - unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec); + unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec, + EndpointFilter * desiredEndpointFilter = nullptr); void LogDetail() const; const char * GetId() const { return mAttributes.id; } @@ -185,10 +196,13 @@ class CastingPlayer : public std::enable_shared_from_this void FindOrEstablishSession(void * clientContext, chip::OnDeviceConnected onDeviceConnected, chip::OnDeviceConnectionFailure onDeviceConnectionFailure); + bool ContainsDesiredEndpoint(core::CastingPlayer * cachedCastingPlayer, EndpointFilter * desiredEndpointFilter); + // ChipDeviceEventHandler handles chip::DeviceLayer::ChipDeviceEvent events and helps the CastingPlayer class commission with // and connect to a CastingPlayer friend class support::ChipDeviceEventHandler; friend class ConnectionContext; + friend class support::EndpointListLoader; }; class ConnectionContext diff --git a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h index ebbb6cfab52e4e..5cfae0bc1e9ae4 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h +++ b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h @@ -23,6 +23,7 @@ #include "Types.h" #include "lib/support/logging/CHIPLogging.h" +#include #include #include @@ -39,8 +40,7 @@ class EndpointAttributes chip::EndpointId id = 0; uint16_t vendorId = 0; uint16_t productId = 0; - uint32_t type = 0; - chip::CharSpan name; + std::vector deviceTypeList; }; class CastingPlayer; @@ -49,13 +49,13 @@ class Endpoint : public std::enable_shared_from_this { private: - memory::Weak mCastingPlayer; + CastingPlayer * mCastingPlayer; std::map> mClusters; EndpointAttributes mAttributes; public: - Endpoint(memory::Weak castingPlayer, const EndpointAttributes & attributes) + Endpoint(CastingPlayer * castingPlayer, const EndpointAttributes & attributes) { this->mCastingPlayer = castingPlayer; this->mAttributes = attributes; @@ -68,7 +68,7 @@ class Endpoint : public std::enable_shared_from_this void operator=(const Endpoint &) = delete; protected: - memory::Strong GetCastingPlayer() const { return mCastingPlayer.lock(); } + CastingPlayer * GetCastingPlayer() const { return mCastingPlayer; } public: /** @@ -78,13 +78,14 @@ class Endpoint : public std::enable_shared_from_this chip::EndpointId GetId() const { return mAttributes.id; } - chip::CharSpan GetName() const { return mAttributes.name; } - uint16_t GetProductId() const { return mAttributes.productId; } uint16_t GetVendorId() const { return mAttributes.vendorId; } - uint32_t GetType() const { return mAttributes.type; } + std::vector GetDeviceTypeList() const + { + return mAttributes.deviceTypeList; + } public: template diff --git a/examples/tv-casting-app/tv-casting-common/core/Types.h b/examples/tv-casting-app/tv-casting-common/core/Types.h index 691fc3d2b36944..267b09642171b2 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Types.h +++ b/examples/tv-casting-app/tv-casting-common/core/Types.h @@ -49,6 +49,8 @@ class AppParameters; class ByteSpanDataProvider; class ServerInitParamsProvider; +class EndpointListLoader; + } // namespace support }; // namespace casting diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp index 897ac1fa4dbb75..0cf5a890c2d3e8 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp @@ -68,13 +68,11 @@ void ChipDeviceEventHandler::Handle(const chip::DeviceLayer::ChipDeviceEvent * e ChipLogProgress(AppServer, "ChipDeviceEventHandler::Handle: Connection to CastingPlayer successful"); CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_CONNECTED; - EndpointListLoader::GetInstance()->Initialize(CastingPlayer::GetTargetCastingPlayer(), &exchangeMgr, - &sessionHandle); - EndpointListLoader::GetInstance()->Load(); + EndpointListLoader::GetInstance()->Initialize(&exchangeMgr, &sessionHandle); - support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); - VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); - CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); + // this will load all the endpoints with their respective attributes into the TargetCastingPlayer + // and call mOnCompleted() + EndpointListLoader::GetInstance()->Load(); }, [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { ChipLogError(AppServer, "ChipDeviceEventHandler::Handle: Connection to CastingPlayer failed"); diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h index 69142603ce40f0..e6759856be8caf 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h @@ -27,7 +27,7 @@ namespace support { /** * @brief Handles chip::DeviceLayer::ChipDeviceEvent events (such as kFailSafeTimerExpired, kBindingsChangedViaCluster, * kCommissioningComplete) sent by the Matter DeviceLayer. - * ChipDeviceEventHandlerhelps the CastingPlayer class commission with and connect to a CastingPlayer + * ChipDeviceEventHandler helps the CastingPlayer class commission with and connect to a CastingPlayer */ class ChipDeviceEventHandler { diff --git a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp index cea9675756d700..a61b23b44d2cb8 100644 --- a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp @@ -43,11 +43,36 @@ EndpointListLoader * EndpointListLoader::GetInstance() return _endpointListLoader; } +void EndpointListLoader::Initialize(chip::Messaging::ExchangeManager * exchangeMgr, const chip::SessionHandle * sessionHandle) +{ + mExchangeMgr = exchangeMgr; + mSessionHandle = sessionHandle; + + for (const auto & binding : chip::BindingTable::GetInstance()) + { + if (binding.type == EMBER_UNICAST_BINDING && CastingPlayer::GetTargetCastingPlayer()->GetNodeId() == binding.nodeId) + { + // check to see if we discovered a new endpoint in the bindings + chip::EndpointId endpointId = binding.remote; + std::vector> endpoints = CastingPlayer::GetTargetCastingPlayer()->GetEndpoints(); + if (std::find_if(endpoints.begin(), endpoints.end(), [&endpointId](const memory::Strong & endpoint) { + return endpoint->GetId() == endpointId; + }) == endpoints.end()) + { + mNewEndpointsToLoad++; + } + } + } + + mPendingAttributeReads = mNewEndpointsToLoad * kTotalDesiredAttributes; + mEndpointAttributesList = new EndpointAttributes[mNewEndpointsToLoad]; +} + CHIP_ERROR EndpointListLoader::Load() { ChipLogProgress(AppServer, "EndpointListLoader::Load() called"); - VerifyOrReturnError(mCastingPlayer != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(CastingPlayer::GetTargetCastingPlayer() != nullptr, CHIP_ERROR_INCORRECT_STATE); int endpointIndex = -1; for (const auto & binding : chip::BindingTable::GetInstance()) @@ -57,11 +82,11 @@ CHIP_ERROR EndpointListLoader::Load() " groupId=%d local endpoint=%d remote endpoint=%d cluster=" ChipLogFormatMEI, binding.type, binding.fabricIndex, ChipLogValueX64(binding.nodeId), binding.groupId, binding.local, binding.remote, ChipLogValueMEI(binding.clusterId.ValueOr(0))); - if (binding.type == EMBER_UNICAST_BINDING && mCastingPlayer->GetNodeId() == binding.nodeId) + if (binding.type == EMBER_UNICAST_BINDING && CastingPlayer::GetTargetCastingPlayer()->GetNodeId() == binding.nodeId) { // if we discovered a new Endpoint from the bindings, read its EndpointAttributes chip::EndpointId endpointId = binding.remote; - std::vector> endpoints = mCastingPlayer->GetEndpoints(); + std::vector> endpoints = CastingPlayer::GetTargetCastingPlayer()->GetEndpoints(); if (std::find_if(endpoints.begin(), endpoints.end(), [&endpointId](const memory::Strong & endpoint) { return endpoint->GetId() == endpointId; }) == endpoints.end()) @@ -70,6 +95,8 @@ CHIP_ERROR EndpointListLoader::Load() ChipLogProgress(AppServer, "EndpointListLoader::Load Reading attributes for endpointId %d", endpointId); mEndpointAttributesList[++endpointIndex].id = endpointId; ReadVendorId(&mEndpointAttributesList[endpointIndex]); + ReadProductId(&mEndpointAttributesList[endpointIndex]); + ReadDeviceTypeList(&mEndpointAttributesList[endpointIndex]); } } } @@ -77,6 +104,35 @@ CHIP_ERROR EndpointListLoader::Load() return CHIP_NO_ERROR; } +void EndpointListLoader::Complete() +{ + ChipLogProgress(AppServer, "EndpointListLoader::Complete called with mPendingAttributeReads %lu", mPendingAttributeReads); + mPendingAttributeReads--; + if (mPendingAttributeReads == 0) + { + ChipLogProgress(AppServer, "EndpointListLoader::Completed with vendorId at index 2: %d", + mEndpointAttributesList[2].vendorId); + + for (unsigned long i = 0; i < mNewEndpointsToLoad; i++) + { + EndpointAttributes endpointAttributes = mEndpointAttributesList[i]; + std::shared_ptr endpoint(new Endpoint(CastingPlayer::GetTargetCastingPlayer(), endpointAttributes)); + CastingPlayer::GetTargetCastingPlayer()->RegisterEndpoint(endpoint); + } + + // cleanup + delete mEndpointAttributesList; + mEndpointAttributesList = nullptr; + mExchangeMgr = nullptr; + mSessionHandle = nullptr; + mEndpointAttributesList = 0; + + support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); + } +} + CHIP_ERROR EndpointListLoader::ReadVendorId(EndpointAttributes * endpointAttributes) { MediaClusterBase cluster(*mExchangeMgr, *mSessionHandle, endpointAttributes->id); @@ -115,38 +171,28 @@ CHIP_ERROR EndpointListLoader::ReadProductId(EndpointAttributes * endpointAttrib }); } -void EndpointListLoader::Initialize(core::CastingPlayer * castingPlayer, chip::Messaging::ExchangeManager * exchangeMgr, - const chip::SessionHandle * sessionHandle) +CHIP_ERROR EndpointListLoader::ReadDeviceTypeList(EndpointAttributes * endpointAttributes) { - mCastingPlayer = castingPlayer; - mExchangeMgr = exchangeMgr; - mSessionHandle = sessionHandle; + MediaClusterBase cluster(*mExchangeMgr, *mSessionHandle, endpointAttributes->id); - for (const auto & binding : chip::BindingTable::GetInstance()) - { - if (binding.type == EMBER_UNICAST_BINDING && mCastingPlayer->GetNodeId() == binding.nodeId) - { - // check to see if we discovered a new endpoint in the bindings - chip::EndpointId endpointId = binding.remote; - std::vector> endpoints = mCastingPlayer->GetEndpoints(); - if (std::find_if(endpoints.begin(), endpoints.end(), [&endpointId](const memory::Strong & endpoint) { - return endpoint->GetId() == endpointId; - }) == endpoints.end()) + return cluster.template ReadAttribute( + endpointAttributes, + [](void * context, + chip::app::Clusters::Descriptor::Attributes::DeviceTypeList::TypeInfo::DecodableArgType decodableDeviceTypeList) { + ChipLogProgress(AppServer, "EndpointListLoader::Load ReadAttribute success"); + EndpointAttributes * endpointAttributes = static_cast(context); + auto iter = decodableDeviceTypeList.begin(); + while (iter.Next()) { - mNewEndpointsToLoad++; + auto & deviceType = iter.GetValue(); + endpointAttributes->deviceTypeList.push_back(deviceType); } - } - } - - mPendingAttributeReads = mNewEndpointsToLoad * kTotalDesiredAttributes; - mEndpointAttributesList = new EndpointAttributes[mNewEndpointsToLoad]; -} - -void EndpointListLoader::Complete() -{ - ChipLogProgress(AppServer, "EndpointListLoader::Complete called with mPendingAttributeReads %lu", mPendingAttributeReads); - mPendingAttributeReads--; - ChipLogProgress(AppServer, "EndpointListLoader::Complete vendorId at index 2: %d", mEndpointAttributesList[2].vendorId); + EndpointListLoader::GetInstance()->Complete(); + }, + [](void * context, CHIP_ERROR err) { + ChipLogProgress(AppServer, "EndpointListLoader::Load ReadAttribute failure"); + EndpointListLoader::GetInstance()->Complete(); + }); } }; // namespace support diff --git a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h index 3403dcba003d36..89a0ec658ce4a6 100644 --- a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h +++ b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h @@ -18,7 +18,6 @@ #pragma once -#include "core/CastingPlayer.h" #include "core/Endpoint.h" #include "core/Types.h" @@ -43,7 +42,7 @@ enum DesiredAttributes { kVendorId = 0, kProductId, - kDeviceType, + kDeviceTypeList, kTotalDesiredAttributes }; @@ -53,24 +52,23 @@ class EndpointListLoader public: static EndpointListLoader * GetInstance(); - void Initialize(core::CastingPlayer * castingPlayer, chip::Messaging::ExchangeManager * exchangeMgr, - const chip::SessionHandle * sessionHandle); + void Initialize(chip::Messaging::ExchangeManager * exchangeMgr, const chip::SessionHandle * sessionHandle); CHIP_ERROR Load(); private: EndpointListLoader(); static EndpointListLoader * _endpointListLoader; + void Complete(); CHIP_ERROR ReadVendorId(core::EndpointAttributes * endpointAttributes); CHIP_ERROR ReadProductId(core::EndpointAttributes * endpointAttributes); - void Complete(); + CHIP_ERROR ReadDeviceTypeList(core::EndpointAttributes * endpointAttributes); - core::CastingPlayer * mCastingPlayer; - chip::Messaging::ExchangeManager * mExchangeMgr; - const chip::SessionHandle * mSessionHandle; - unsigned long mNewEndpointsToLoad = 0; - unsigned long mPendingAttributeReads = 0; - core::EndpointAttributes * mEndpointAttributesList; + chip::Messaging::ExchangeManager * mExchangeMgr = nullptr; + const chip::SessionHandle * mSessionHandle = nullptr; + unsigned long mNewEndpointsToLoad = 0; + unsigned long mPendingAttributeReads = 0; + core::EndpointAttributes * mEndpointAttributesList = nullptr; }; }; // namespace support