| /** |
| * Copyright (c) 2024 Project CHIP Authors |
| * |
| * 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. |
| */ |
| |
| #import "MTRBaseDevice_Internal.h" |
| #import "MTRDefines_Internal.h" |
| #import "MTRDeviceController_Internal.h" |
| #import "MTRLogging_Internal.h" |
| #import "MTRServerAttribute_Internal.h" |
| #import "MTRServerEndpoint_Internal.h" |
| #import "MTRUnfairLock.h" |
| #import "NSDataSpanConversion.h" |
| |
| #import <Matter/MTRServerAttribute.h> |
| |
| #include <app/reporting/reporting.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/DataModelTypes.h> |
| #include <lib/support/SafeInt.h> |
| |
| using namespace chip; |
| |
| MTR_DIRECT_MEMBERS |
| @implementation MTRServerAttribute { |
| // _lock always protects access to _deviceController, _value, |
| // _serializedValue, and _parentCluster. |
| os_unfair_lock _lock; |
| MTRDeviceController * __weak _deviceController; |
| NSDictionary<NSString *, id> * _value; |
| app::ConcreteClusterPath _parentCluster; |
| } |
| |
| - (nullable instancetype)initAttributeWithID:(NSNumber *)attributeID initialValue:(NSDictionary<NSString *, id> *)value requiredReadPrivilege:(MTRAccessControlEntryPrivilege)requiredReadPrivilege writable:(BOOL)writable |
| { |
| auto attrIDValue = attributeID.unsignedLongLongValue; |
| if (!CanCastTo<AttributeId>(attrIDValue)) { |
| MTR_LOG_ERROR("MTRServerAttribute provided too-large attribute ID: 0x%llx", attrIDValue); |
| return nil; |
| } |
| |
| auto attrId = static_cast<AttributeId>(attrIDValue); |
| if (!IsValidAttributeId(attrId)) { |
| MTR_LOG_ERROR("MTRServerAttribute provided invalid attribute ID: 0x%" PRIx32, attrId); |
| return nil; |
| } |
| |
| return [self initWithAttributeID:[attributeID copy] value:[value copy] requiredReadPrivilege:requiredReadPrivilege writable:writable]; |
| } |
| |
| - (nullable instancetype)initReadonlyAttributeWithID:(NSNumber *)attributeID initialValue:(NSDictionary<NSString *, id> *)value requiredPrivilege:(MTRAccessControlEntryPrivilege)requiredPrivilege |
| { |
| return [self initAttributeWithID:attributeID initialValue:value requiredReadPrivilege:requiredPrivilege writable:NO]; |
| } |
| |
| // initWithAttributeID:value:serializedValue:requiredReadPrivilege:writable: |
| // assumes that the attribute ID, value, serializedValue, have already been |
| // validated and, if needed, copied from the input. |
| - (nullable instancetype)initWithAttributeID:(NSNumber *)attributeID value:(NSDictionary<NSString *, id> *)value requiredReadPrivilege:(MTRAccessControlEntryPrivilege)requiredReadPrivilege writable:(BOOL)writable |
| { |
| if (!(self = [super init])) { |
| return nil; |
| } |
| |
| _lock = OS_UNFAIR_LOCK_INIT; |
| _attributeID = attributeID; |
| _requiredReadPrivilege = requiredReadPrivilege; |
| _writable = writable; |
| _parentCluster = app::ConcreteClusterPath(kInvalidEndpointId, kInvalidClusterId); |
| |
| // Now call setValue to store the value and its serialization. |
| if ([self setValueInternal:value logIfNotAssociated:NO] == NO) { |
| return nil; |
| } |
| |
| return self; |
| } |
| |
| - (BOOL)setValue:(NSDictionary<NSString *, id> *)value |
| { |
| return [self setValueInternal:value logIfNotAssociated:YES]; |
| } |
| |
| - (BOOL)setValueInternal:(NSDictionary<NSString *, id> *)value logIfNotAssociated:(BOOL)logIfNotAssociated |
| { |
| id serializedValue; |
| id dataType = value[MTRTypeKey]; |
| if ([MTRArrayValueType isEqual:dataType]) { |
| id dataValue = value[MTRValueKey]; |
| if (![dataValue isKindOfClass:NSArray.class]) { |
| MTR_LOG_ERROR("MTRServerAttribute value claims to be a list but isn't: %@", value); |
| return NO; |
| } |
| NSArray * dataValueList = dataValue; |
| auto * listValue = [NSMutableArray arrayWithCapacity:dataValueList.count]; |
| if (listValue == nil) { |
| return NO; |
| } |
| for (id item in dataValueList) { |
| if (![item isKindOfClass:NSDictionary.class]) { |
| MTR_LOG_ERROR("MTRServerAttribute value array should contain dictionaries"); |
| } |
| NSDictionary<NSString *, id> * itemDictionary = item; |
| |
| NSError * encodingError; |
| NSData * encodedItem = MTREncodeTLVFromDataValueDictionary(itemDictionary[MTRDataKey], &encodingError); |
| if (encodedItem == nil) { |
| return NO; |
| } |
| [listValue addObject:encodedItem]; |
| } |
| serializedValue = listValue; |
| } else { |
| NSError * encodingError; |
| serializedValue = MTREncodeTLVFromDataValueDictionary(value, &encodingError); |
| if (serializedValue == nil) { |
| return NO; |
| } |
| } |
| |
| // We serialized properly, so should be good to go on the value. Lock |
| // around our ivar accesses. |
| std::lock_guard lock(_lock); |
| |
| _value = [value copy]; |
| |
| MTR_LOG_DEFAULT("Attribute value updated: %@", [self _descriptionWhileLocked]); // Logs new value as part of our description. |
| |
| MTRDeviceController * deviceController = _deviceController; |
| if (deviceController == nil) { |
| // We're not bound to a controller, so safe to directly update |
| // _serializedValue. |
| if (logIfNotAssociated) { |
| MTR_LOG_DEFAULT("Not publishing value for attribute " ChipLogFormatMEI "; not bound to a controller", |
| ChipLogValueMEI(static_cast<AttributeId>(_attributeID.unsignedLongLongValue))); |
| } |
| _serializedValue = serializedValue; |
| } else { |
| [deviceController asyncDispatchToMatterQueue:^{ |
| std::lock_guard lock(self->_lock); |
| auto changed = ![self->_serializedValue isEqual:serializedValue]; |
| self->_serializedValue = serializedValue; |
| if (changed) { |
| MatterReportingAttributeChangeCallback(self->_parentCluster.mEndpointId, self->_parentCluster.mClusterId, static_cast<AttributeId>(self->_attributeID.unsignedLongLongValue)); |
| } |
| } |
| errorHandler:nil]; |
| } |
| |
| return YES; |
| } |
| |
| - (NSDictionary<NSString *, id> *)value |
| { |
| std::lock_guard lock(_lock); |
| return [_value copy]; |
| } |
| |
| - (BOOL)associateWithController:(nullable MTRDeviceController *)controller |
| { |
| std::lock_guard lock(_lock); |
| |
| MTRDeviceController * existingController = _deviceController; |
| if (existingController != nil) { |
| MTR_LOG_ERROR("Cannot associate MTRServerAttribute with controller %@; already associated with controller %@", |
| controller.uniqueIdentifier, existingController.uniqueIdentifier); |
| return NO; |
| } |
| |
| _deviceController = controller; |
| |
| MTR_LOG_DEFAULT("Associated %@ with controller", [self _descriptionWhileLocked]); |
| |
| return YES; |
| } |
| |
| - (void)invalidate |
| { |
| std::lock_guard lock(_lock); |
| |
| _deviceController = nil; |
| } |
| |
| - (BOOL)addToCluster:(const app::ConcreteClusterPath &)cluster |
| { |
| std::lock_guard lock(_lock); |
| |
| if (_parentCluster.mClusterId != kInvalidClusterId) { |
| MTR_LOG_ERROR("Cannot add attribute to cluster " ChipLogFormatMEI "; already added to cluster " ChipLogFormatMEI, ChipLogValueMEI(cluster.mClusterId), ChipLogValueMEI(_parentCluster.mClusterId)); |
| return NO; |
| } |
| |
| _parentCluster = cluster; |
| return YES; |
| } |
| |
| - (void)updateParentCluster:(const app::ConcreteClusterPath &)cluster |
| { |
| std::lock_guard lock(_lock); |
| _parentCluster = cluster; |
| } |
| |
| - (const app::ConcreteClusterPath &)parentCluster |
| { |
| std::lock_guard lock(_lock); |
| return _parentCluster; |
| } |
| |
| - (NSString *)description |
| { |
| std::lock_guard lock(_lock); |
| return [self _descriptionWhileLocked]; |
| } |
| |
| - (NSString *)_descriptionWhileLocked |
| { |
| os_unfair_lock_assert_owner(&_lock); |
| return [NSString stringWithFormat:@"<MTRServerAttribute endpoint %u, cluster " ChipLogFormatMEI ", id " ChipLogFormatMEI ", value '%@'>", static_cast<EndpointId>(_parentCluster.mEndpointId), ChipLogValueMEI(_parentCluster.mClusterId), ChipLogValueMEI(_attributeID.unsignedLongLongValue), _value]; |
| } |
| |
| @end |