blob: 638c8d71f8f50a07b3c6db056060c02ed224144d [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <access/AccessConfig.h>
#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
#include "AccessRestrictionProvider.h"
#endif
#include "Privilege.h"
#include "RequestPath.h"
#include "SubjectDescriptor.h"
#include <lib/core/CHIPCore.h>
#include <lib/core/Global.h>
#include <lib/support/CodeUtils.h>
// Dump function for use during development only (0 for disabled, non-zero for enabled).
#define CHIP_ACCESS_CONTROL_DUMP_ENABLED 0
namespace chip {
namespace Access {
class AccessControl
{
public:
/**
* Used by access control to determine if a device type resolves to an endpoint.
*/
struct DeviceTypeResolver
{
public:
virtual ~DeviceTypeResolver() = default;
virtual bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) = 0;
};
/**
* Handle to an entry in the access control list.
*
* Must be prepared (`AccessControl::PrepareEntry`) or read (`AccessControl::ReadEntry`) before first use.
*/
class Entry
{
public:
struct Target
{
using Flags = unsigned;
static constexpr Flags kCluster = 1 << 0;
static constexpr Flags kEndpoint = 1 << 1;
static constexpr Flags kDeviceType = 1 << 2;
Flags flags = 0;
ClusterId cluster;
EndpointId endpoint;
DeviceTypeId deviceType;
};
class Delegate
{
public:
Delegate() = default;
Delegate(const Delegate &) = delete;
Delegate & operator=(const Delegate &) = delete;
virtual ~Delegate() = default;
virtual void Release() {}
// Simple getters
virtual CHIP_ERROR GetAuthMode(AuthMode & authMode) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR GetFabricIndex(FabricIndex & fabricIndex) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR GetPrivilege(Privilege & privilege) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
// Simple setters
virtual CHIP_ERROR SetAuthMode(AuthMode authMode) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR SetFabricIndex(FabricIndex fabricIndex) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR SetPrivilege(Privilege privilege) { return CHIP_ERROR_NOT_IMPLEMENTED; }
// Subjects
virtual CHIP_ERROR GetSubjectCount(size_t & count) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR GetSubject(size_t index, NodeId & subject) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR SetSubject(size_t index, NodeId subject) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR AddSubject(size_t * index, NodeId subject) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR RemoveSubject(size_t index) { return CHIP_ERROR_NOT_IMPLEMENTED; }
// Targets
virtual CHIP_ERROR GetTargetCount(size_t & count) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR GetTarget(size_t index, Target & target) const { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR SetTarget(size_t index, const Target & target) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR AddTarget(size_t * index, const Target & target) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual CHIP_ERROR RemoveTarget(size_t index) { return CHIP_ERROR_NOT_IMPLEMENTED; }
};
Entry() = default;
Entry(Entry && other) : mDelegate(other.mDelegate) { other.mDelegate = &mDefaultDelegate.get(); }
Entry & operator=(Entry && other)
{
if (this != &other)
{
mDelegate->Release();
mDelegate = other.mDelegate;
other.mDelegate = &mDefaultDelegate.get();
}
return *this;
}
Entry(const Entry &) = delete;
Entry & operator=(const Entry &) = delete;
~Entry() { mDelegate->Release(); }
// Simple getters
CHIP_ERROR GetAuthMode(AuthMode & authMode) const { return mDelegate->GetAuthMode(authMode); }
CHIP_ERROR GetFabricIndex(FabricIndex & fabricIndex) const { return mDelegate->GetFabricIndex(fabricIndex); }
CHIP_ERROR GetPrivilege(Privilege & privilege) const { return mDelegate->GetPrivilege(privilege); }
// Simple setters
CHIP_ERROR SetAuthMode(AuthMode authMode) { return mDelegate->SetAuthMode(authMode); }
CHIP_ERROR SetFabricIndex(FabricIndex fabricIndex) { return mDelegate->SetFabricIndex(fabricIndex); }
CHIP_ERROR SetPrivilege(Privilege privilege) { return mDelegate->SetPrivilege(privilege); }
/**
* Gets the number of subjects.
*
* @param [out] count The number of subjects.
*/
CHIP_ERROR GetSubjectCount(size_t & count) const { return mDelegate->GetSubjectCount(count); }
/**
* Gets the specified subject.
*
* @param [in] index The index of the subject to get.
* @param [out] subject The subject into which to get.
*/
CHIP_ERROR GetSubject(size_t index, NodeId & subject) const { return mDelegate->GetSubject(index, subject); }
/**
* Sets the specified subject.
*
* @param [in] index The index of the subject to set.
* @param [in] subject The subject from which to set.
*/
CHIP_ERROR SetSubject(size_t index, NodeId subject) { return mDelegate->SetSubject(index, subject); }
/**
* Adds the specified subject.
*
* @param [out] index The index of the added subject, if not null.
* @param [in] subject The subject to add.
*/
CHIP_ERROR AddSubject(size_t * index, NodeId subject) { return mDelegate->AddSubject(index, subject); }
/**
* Removes the specified subject.
*
* @param [in] index The index of the subject to delete.
*/
CHIP_ERROR RemoveSubject(size_t index) { return mDelegate->RemoveSubject(index); }
/**
* Gets the number of targets.
*
* @param [out] count The number of targets.
*/
CHIP_ERROR GetTargetCount(size_t & count) const { return mDelegate->GetTargetCount(count); }
/**
* Gets the specified target.
*
* @param [in] index The index of the target to get.
* @param [out] target The target into which to get.
*/
CHIP_ERROR GetTarget(size_t index, Target & target) const { return mDelegate->GetTarget(index, target); }
/**
* Sets the specified target.
*
* @param [in] index The index of the target to set.
* @param [in] target The target from which to set.
*/
CHIP_ERROR SetTarget(size_t index, const Target & target) { return mDelegate->SetTarget(index, target); }
/**
* Adds the specified target.
*
* @param [out] index The index of the added target, if not null.
* @param [in] target The target to add.
*/
CHIP_ERROR AddTarget(size_t * index, const Target & target) { return mDelegate->AddTarget(index, target); }
/**
* Removes the specified target.
*
* @param [in] index The index of the target to delete.
*/
CHIP_ERROR RemoveTarget(size_t index) { return mDelegate->RemoveTarget(index); }
bool HasDefaultDelegate() const { return mDelegate == &mDefaultDelegate.get(); }
const Delegate & GetDelegate() const { return *mDelegate; }
Delegate & GetDelegate() { return *mDelegate; }
void SetDelegate(Delegate & delegate)
{
mDelegate->Release();
mDelegate = &delegate;
}
void ResetDelegate()
{
mDelegate->Release();
mDelegate = &mDefaultDelegate.get();
}
private:
static Global<Delegate> mDefaultDelegate;
Delegate * mDelegate = &mDefaultDelegate.get();
};
/**
* Handle to an entry iterator in the access control list.
*
* Must be initialized (`AccessControl::Entries`) before first use.
*/
class EntryIterator
{
public:
class Delegate
{
public:
Delegate() = default;
Delegate(const Delegate &) = delete;
Delegate & operator=(const Delegate &) = delete;
virtual ~Delegate() = default;
virtual void Release() {}
virtual CHIP_ERROR Next(Entry & entry) { return CHIP_ERROR_SENTINEL; }
};
EntryIterator() = default;
EntryIterator(const EntryIterator &) = delete;
EntryIterator & operator=(const EntryIterator &) = delete;
~EntryIterator() { mDelegate->Release(); }
CHIP_ERROR Next(Entry & entry) { return mDelegate->Next(entry); }
const Delegate & GetDelegate() const { return *mDelegate; }
Delegate & GetDelegate() { return *mDelegate; }
void SetDelegate(Delegate & delegate)
{
mDelegate->Release();
mDelegate = &delegate;
}
void ResetDelegate()
{
mDelegate->Release();
mDelegate = &mDefaultDelegate.get();
}
private:
static Global<Delegate> mDefaultDelegate;
Delegate * mDelegate = &mDefaultDelegate.get();
};
/**
* Used by access control to notify of changes in access control list.
*/
class EntryListener
{
public:
enum class ChangeType
{
kAdded = 1,
kRemoved = 2,
kUpdated = 3
};
virtual ~EntryListener() = default;
/**
* Notifies of a change in the access control list.
*
* The fabric is indicated by its own parameter. If available, a subject descriptor will
* have more detail (and its fabric index will match). A best effort is made to provide
* the latest value of the changed entry.
*
* @param [in] subjectDescriptor Optional (if available) subject descriptor for this operation.
* @param [in] fabric Index of fabric in which entry has changed.
* @param [in] index Index of entry to which has changed (relative to fabric).
* @param [in] entry Optional (best effort) latest value of entry which has changed.
* @param [in] changeType Type of change.
*/
virtual void OnEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index,
const Entry * entry, ChangeType changeType) = 0;
private:
EntryListener * mNext = nullptr;
friend class AccessControl;
};
class Delegate
{
public:
Delegate() = default;
Delegate(const Delegate &) = delete;
Delegate & operator=(const Delegate &) = delete;
virtual ~Delegate() = default;
virtual void Release() {}
virtual CHIP_ERROR Init() { return CHIP_NO_ERROR; }
virtual void Finish() {}
// Capabilities
virtual CHIP_ERROR GetMaxEntriesPerFabric(size_t & value) const
{
value = 0;
return CHIP_NO_ERROR;
}
virtual CHIP_ERROR GetMaxSubjectsPerEntry(size_t & value) const
{
value = 0;
return CHIP_NO_ERROR;
}
virtual CHIP_ERROR GetMaxTargetsPerEntry(size_t & value) const
{
value = 0;
return CHIP_NO_ERROR;
}
virtual CHIP_ERROR GetMaxEntryCount(size_t & value) const
{
value = 0;
return CHIP_NO_ERROR;
}
// Actualities
virtual CHIP_ERROR GetEntryCount(FabricIndex fabric, size_t & value) const
{
value = 0;
return CHIP_NO_ERROR;
}
virtual CHIP_ERROR GetEntryCount(size_t & value) const
{
value = 0;
return CHIP_NO_ERROR;
}
// Preparation
virtual CHIP_ERROR PrepareEntry(Entry & entry) { return CHIP_NO_ERROR; }
// CRUD
virtual CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, FabricIndex * fabricIndex) { return CHIP_NO_ERROR; }
virtual CHIP_ERROR ReadEntry(size_t index, Entry & entry, const FabricIndex * fabricIndex) const { return CHIP_NO_ERROR; }
virtual CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex * fabricIndex) { return CHIP_NO_ERROR; }
virtual CHIP_ERROR DeleteEntry(size_t index, const FabricIndex * fabricIndex) { return CHIP_NO_ERROR; }
// Iteration
virtual CHIP_ERROR Entries(EntryIterator & iterator, const FabricIndex * fabricIndex) const { return CHIP_NO_ERROR; }
// Check
// Return CHIP_NO_ERROR if allowed, CHIP_ERROR_ACCESS_DENIED if denied,
// CHIP_ERROR_NOT_IMPLEMENTED to use the default check algorithm (against entries),
// or any other CHIP_ERROR if another error occurred.
virtual CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath,
Privilege requestPrivilege)
{
return CHIP_ERROR_ACCESS_DENIED;
}
};
AccessControl() = default;
AccessControl(const AccessControl &) = delete;
AccessControl & operator=(const AccessControl &) = delete;
~AccessControl()
{
// Never-initialized AccessControl instances will not have the delegate set.
if (IsInitialized())
{
mDelegate->Release();
}
}
/**
* Initialize the access control module. Must be called before first use.
*
* @return CHIP_NO_ERROR on success, CHIP_ERROR_INCORRECT_STATE if called more than once,
* CHIP_ERROR_INVALID_ARGUMENT if delegate is null, or other fatal error.
*/
CHIP_ERROR Init(AccessControl::Delegate * delegate, DeviceTypeResolver & deviceTypeResolver);
/**
* Deinitialize the access control module. Must be called when finished.
*/
void Finish();
// Capabilities
CHIP_ERROR GetMaxEntriesPerFabric(size_t & value) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->GetMaxEntriesPerFabric(value);
}
CHIP_ERROR GetMaxSubjectsPerEntry(size_t & value) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->GetMaxSubjectsPerEntry(value);
}
CHIP_ERROR GetMaxTargetsPerEntry(size_t & value) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->GetMaxTargetsPerEntry(value);
}
CHIP_ERROR GetMaxEntryCount(size_t & value) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->GetMaxEntryCount(value);
}
// Actualities
CHIP_ERROR GetEntryCount(FabricIndex fabric, size_t & value) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->GetEntryCount(fabric, value);
}
CHIP_ERROR GetEntryCount(size_t & value) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->GetEntryCount(value);
}
/**
* Prepares an entry.
*
* An entry must be prepared or read (`ReadEntry`) before first use.
*
* @param [in] entry Entry to prepare.
*/
CHIP_ERROR PrepareEntry(Entry & entry)
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->PrepareEntry(entry);
}
/**
* Creates an entry in the access control list.
*
* @param [in] subjectDescriptor Optional subject descriptor for this operation.
* @param [in] fabric Index of fabric in which to create entry.
* @param [out] index (If not nullptr) index of created entry (relative to fabric).
* @param [in] entry Entry from which created entry is copied.
*/
CHIP_ERROR CreateEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t * index, const Entry & entry);
/**
* Creates an entry in the access control list.
*
* @param [out] index Entry index of created entry, if not null. May be relative to `fabricIndex`.
* @param [in] entry Entry from which to copy.
* @param [out] fabricIndex Fabric index of created entry, if not null, in which case entry `index` will be relative to fabric.
*/
CHIP_ERROR CreateEntry(size_t * index, const Entry & entry, FabricIndex * fabricIndex = nullptr)
{
VerifyOrReturnError(IsValid(entry), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->CreateEntry(index, entry, fabricIndex);
}
/**
* Reads an entry in the access control list.
*
* @param [in] fabric Index of fabric in which to read entry.
* @param [in] index Index of entry to read (relative to fabric).
* @param [in] entry Entry into which read entry is copied.
*/
CHIP_ERROR ReadEntry(FabricIndex fabric, size_t index, Entry & entry) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->ReadEntry(index, entry, &fabric);
}
/**
* Reads an entry from the access control list.
*
* @param [in] index Entry index of entry to read. May be relative to `fabricIndex`.
* @param [out] entry Entry into which to copy.
* @param [in] fabricIndex Fabric to which entry `index` is relative, if not null.
*/
CHIP_ERROR ReadEntry(size_t index, Entry & entry, const FabricIndex * fabricIndex = nullptr) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->ReadEntry(index, entry, fabricIndex);
}
/**
* Updates an entry in the access control list.
*
* @param [in] subjectDescriptor Optional subject descriptor for this operation.
* @param [in] fabric Index of fabric in which to update entry.
* @param [in] index Index of entry to update (relative to fabric).
* @param [in] entry Entry from which updated entry is copied.
*/
CHIP_ERROR UpdateEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry & entry);
/**
* Updates an entry in the access control list.
*
* @param [in] index Entry index of entry to update, if not null. May be relative to `fabricIndex`.
* @param [in] entry Entry from which to copy.
* @param [in] fabricIndex Fabric to which entry `index` is relative, if not null.
*/
CHIP_ERROR UpdateEntry(size_t index, const Entry & entry, const FabricIndex * fabricIndex = nullptr)
{
VerifyOrReturnError(IsValid(entry), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->UpdateEntry(index, entry, fabricIndex);
}
/**
* Deletes an entry in the access control list.
*
* @param [in] subjectDescriptor Optional subject descriptor for this operation.
* @param [in] fabric Index of fabric in which to delete entry.
* @param [in] index Index of entry to delete (relative to fabric).
*/
CHIP_ERROR DeleteEntry(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index);
/**
* Deletes an entry from the access control list.
*
* @param [in] index Entry index of entry to delete. May be relative to `fabricIndex`.
* @param [in] fabricIndex Fabric to which entry `index` is relative, if not null.
*/
CHIP_ERROR DeleteEntry(size_t index, const FabricIndex * fabricIndex = nullptr)
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->DeleteEntry(index, fabricIndex);
}
/**
* @brief Remove all ACL entries for the given fabricIndex
*
* @param[in] fabricIndex fabric index for which to remove all entries
*/
CHIP_ERROR DeleteAllEntriesForFabric(FabricIndex fabricIndex)
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR stickyError = CHIP_NO_ERROR;
// Remove access control entries in reverse order (it could be any order, but reverse order
// will cause less churn in persistent storage).
size_t aclCount = 0;
if (GetEntryCount(fabricIndex, aclCount) == CHIP_NO_ERROR)
{
while (aclCount)
{
CHIP_ERROR err = DeleteEntry(nullptr, fabricIndex, --aclCount);
stickyError = (stickyError == CHIP_NO_ERROR) ? err : stickyError;
}
}
return stickyError;
}
/**
* Iterates over entries in the access control list.
*
* @param [in] fabric Fabric over which to iterate entries.
* @param [out] iterator Iterator controlling the iteration.
*/
CHIP_ERROR Entries(FabricIndex fabric, EntryIterator & iterator) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->Entries(iterator, &fabric);
}
/**
* Iterates over entries in the access control list.
*
* @param [out] iterator Iterator controlling the iteration.
* @param [in] fabricIndex Iteration is confined to fabric, if not null.
*/
CHIP_ERROR Entries(EntryIterator & iterator, const FabricIndex * fabricIndex = nullptr) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
return mDelegate->Entries(iterator, fabricIndex);
}
// Adds a listener to the end of the listener list, if not already in the list.
void AddEntryListener(EntryListener & listener);
// Removes a listener from the listener list, if in the list.
void RemoveEntryListener(EntryListener & listener);
#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
// Set an optional AcceessRestriction object for MNGD feature.
void SetAccessRestrictionProvider(AccessRestrictionProvider * accessRestrictionProvider)
{
mAccessRestrictionProvider = accessRestrictionProvider;
}
AccessRestrictionProvider * GetAccessRestrictionProvider() { return mAccessRestrictionProvider; }
#endif
/**
* Check whether or not Access Restriction List is supported.
*
* @retval true if Access Restriction List is supported.
*/
bool IsAccessRestrictionListSupported() const;
/**
* Check whether access (by a subject descriptor, to a request path,
* requiring a privilege) should be allowed or denied.
*
* If an AccessRestrictionProvider object is set, it will be checked for additional access restrictions.
*
* @retval #CHIP_ERROR_ACCESS_DENIED if denied.
* @retval other errors should also be treated as denied.
* @retval #CHIP_NO_ERROR if allowed.
*/
CHIP_ERROR Check(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege);
#if CHIP_ACCESS_CONTROL_DUMP_ENABLED
CHIP_ERROR Dump(const Entry & entry);
#endif
private:
bool IsInitialized() const { return (mDelegate != nullptr); }
bool IsValid(const Entry & entry);
void NotifyEntryChanged(const SubjectDescriptor * subjectDescriptor, FabricIndex fabric, size_t index, const Entry * entry,
EntryListener::ChangeType changeType);
/**
* Check ACL for whether access (by a subject descriptor, to a request path,
* requiring a privilege) should be allowed or denied.
*/
CHIP_ERROR CheckACL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege);
/**
* Check CommissioningARL or ARL (as appropriate) for whether access (by a
* subject descriptor, to a request path, requiring a privilege) should
* be allowed or denied.
*/
CHIP_ERROR CheckARL(const SubjectDescriptor & subjectDescriptor, const RequestPath & requestPath, Privilege requestPrivilege);
private:
Delegate * mDelegate = nullptr;
DeviceTypeResolver * mDeviceTypeResolver = nullptr;
EntryListener * mEntryListener = nullptr;
#if CHIP_CONFIG_USE_ACCESS_RESTRICTIONS
AccessRestrictionProvider * mAccessRestrictionProvider;
#endif
};
/**
* Get the global instance set by SetAccessControl, or the default.
*
* Calls to this function must be synchronized externally.
*/
AccessControl & GetAccessControl();
/**
* Set the global instance returned by GetAccessControl.
*
* Calls to this function must be synchronized externally.
*/
void SetAccessControl(AccessControl & accessControl);
/**
* Reset the global instance to the default.
*
* Calls to this function must be synchronized externally.
*/
void ResetAccessControlToDefault();
} // namespace Access
} // namespace chip