/* * * Copyright (c) 2021-2023 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using chip::Protocols::InteractionModel::Status; using namespace chip; using namespace chip::app; using namespace chip::Access; using namespace chip::app::Compatibility; using namespace chip::app::Compatibility::Internal; namespace chip { namespace app { namespace { template CHIP_ERROR attributeBufferToNumericTlvData(TLV::TLVWriter & writer, bool isNullable) { typename NumericAttributeTraits::StorageType value; memcpy(&value, gEmberAttributeIOBufferSpan.data(), sizeof(value)); TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); if (isNullable && NumericAttributeTraits::IsNullValue(value)) { return writer.PutNull(tag); } if (!NumericAttributeTraits::CanRepresentValue(isNullable, value)) { return CHIP_ERROR_INCORRECT_STATE; } return NumericAttributeTraits::Encode(writer, tag, value); } } // anonymous namespace Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath) { using Protocols::InteractionModel::Status; const EmberAfEndpointType * type = emberAfFindEndpointType(aCommandPath.mEndpointId); if (type == nullptr) { return Status::UnsupportedEndpoint; } const EmberAfCluster * cluster = emberAfFindClusterInType(type, aCommandPath.mClusterId, CLUSTER_MASK_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; } auto * commandHandler = CommandHandlerInterfaceRegistry::Instance().GetCommandHandler(aCommandPath.mEndpointId, aCommandPath.mClusterId); if (commandHandler) { struct Context { bool commandExists; CommandId targetCommand; } context{ false, aCommandPath.mCommandId }; CHIP_ERROR err = commandHandler->EnumerateAcceptedCommands( aCommandPath, [](CommandId command, void * closure) -> Loop { auto * ctx = static_cast(closure); if (ctx->targetCommand == command) { ctx->commandExists = true; return Loop::Break; } return Loop::Continue; }, &context); // We now have three cases: // 1) handler returned CHIP_ERROR_NOT_IMPLEMENTED. In that case we // should fall back to looking at cluster->acceptedCommandList // 2) handler returned success. In that case, the handler is the source // of truth about the set of accepted commands, and // context.commandExists indicates whether a aCommandPath.mCommandId // was in the set, and we should return either Success or // UnsupportedCommand accordingly. // 3) Some other status was returned. In this case we should probably // err on the side of not allowing the command, since we have no idea // whether to allow it or not. if (err != CHIP_ERROR_NOT_IMPLEMENTED) { if (err == CHIP_NO_ERROR) { return context.commandExists ? Status::Success : Status::UnsupportedCommand; } return Status::Failure; } } for (const CommandId * cmd = cluster->acceptedCommandList; cmd != nullptr && *cmd != kInvalidCommandId; cmd++) { if (*cmd == aCommandPath.mCommandId) { return Status::Success; } } return Status::UnsupportedCommand; } namespace { CHIP_ERROR ReadClusterDataVersion(const ConcreteClusterPath & aConcreteClusterPath, DataVersion & aDataVersion) { DataVersion * version = emberAfDataVersionStorage(aConcreteClusterPath); if (version == nullptr) { ChipLogError(DataManagement, "Endpoint %x, Cluster " ChipLogFormatMEI " not found in ReadClusterDataVersion!", aConcreteClusterPath.mEndpointId, ChipLogValueMEI(aConcreteClusterPath.mClusterId)); return CHIP_ERROR_NOT_FOUND; } aDataVersion = *version; return CHIP_NO_ERROR; } // Helper function for trying to read an attribute value via an // AttributeAccessInterface. On failure, the read has failed. On success, the // aTriedEncode outparam is set to whether the AttributeAccessInterface tried to encode a value. CHIP_ERROR ReadViaAccessInterface(const SubjectDescriptor & subjectDescriptor, bool aIsFabricFiltered, const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, AttributeEncodeState * aEncoderState, AttributeAccessInterface * aAccessInterface, bool * aTriedEncode) { AttributeEncodeState state(aEncoderState); DataVersion version = 0; ReturnErrorOnFailure(ReadClusterDataVersion(aPath, version)); AttributeValueEncoder valueEncoder(aAttributeReports, subjectDescriptor, aPath, version, aIsFabricFiltered, state); CHIP_ERROR err = aAccessInterface->Read(aPath, valueEncoder); if (err == CHIP_IM_GLOBAL_STATUS(UnsupportedRead) && aPath.mExpanded) { // // Set this to true to ensure our caller will return immediately without proceeding further. // *aTriedEncode = true; return CHIP_NO_ERROR; } if (err != CHIP_NO_ERROR) { // If the err is not CHIP_NO_ERROR, means the encoding was aborted, then the valueEncoder may save its state. // The state is used by list chunking feature for now. if (aEncoderState != nullptr) { *aEncoderState = valueEncoder.GetState(); } return err; } *aTriedEncode = valueEncoder.TriedEncode(); return CHIP_NO_ERROR; } // Determine the appropriate status response for an unsupported attribute for // the given path. Must be called when the attribute is known to be unsupported // (i.e. we found no attribute metadata for it). Protocols::InteractionModel::Status UnsupportedAttributeStatus(const ConcreteAttributePath & aPath) { using Protocols::InteractionModel::Status; const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); if (type == nullptr) { return Status::UnsupportedEndpoint; } const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; } // Since we know the attribute is unsupported and the endpoint/cluster are // OK, this is the only option left. return Status::UnsupportedAttribute; } // Will set at most one of the out-params (aAttributeCluster or // aAttributeMetadata) to non-null. Both null means attribute not supported, // aAttributeCluster non-null means this is a supported global attribute that // does not have metadata. void FindAttributeMetadata(const ConcreteAttributePath & aPath, const EmberAfCluster ** aAttributeCluster, const EmberAfAttributeMetadata ** aAttributeMetadata) { *aAttributeCluster = nullptr; *aAttributeMetadata = nullptr; for (auto & attr : GlobalAttributesNotInMetadata) { if (attr == aPath.mAttributeId) { *aAttributeCluster = emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId); return; } } *aAttributeMetadata = emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); } } // anonymous namespace bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath) { for (auto & attr : GlobalAttributesNotInMetadata) { if (attr == aPath.mAttributeId) { return (emberAfFindServerCluster(aPath.mEndpointId, aPath.mClusterId) != nullptr); } } return (emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId) != nullptr); } CHIP_ERROR ReadSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, AttributeEncodeState * apEncoderState) { ChipLogDetail(DataManagement, "Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%x AttributeId=" ChipLogFormatMEI " (expanded=%d)", ChipLogValueMEI(aPath.mClusterId), aPath.mEndpointId, ChipLogValueMEI(aPath.mAttributeId), aPath.mExpanded); // Check attribute existence. This includes attributes with registered metadata, but also specially handled // mandatory global attributes (which just check for cluster on endpoint). const EmberAfCluster * attributeCluster = nullptr; const EmberAfAttributeMetadata * attributeMetadata = nullptr; FindAttributeMetadata(aPath, &attributeCluster, &attributeMetadata); if (attributeCluster == nullptr && attributeMetadata == nullptr) { return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(UnsupportedAttributeStatus(aPath)); } // Check access control. A failed check will disallow the operation, and may or may not generate an attribute report // depending on whether the path was expanded. { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId, .requestType = Access::RequestType::kAttributeReadRequest, .entityId = aPath.mAttributeId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForReadAttribute(aPath); CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, requestPrivilege); if (err != CHIP_NO_ERROR) { VerifyOrReturnError((err == CHIP_ERROR_ACCESS_DENIED) || (err == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL), err); if (aPath.mExpanded) { return CHIP_NO_ERROR; } return err == CHIP_ERROR_ACCESS_DENIED ? CHIP_IM_GLOBAL_STATUS(UnsupportedAccess) : CHIP_IM_GLOBAL_STATUS(AccessRestricted); } } { // Special handling for mandatory global attributes: these are always for attribute list, using a special // reader (which can be lightweight constructed even from nullptr). GlobalAttributeReader reader(attributeCluster); AttributeAccessInterface * attributeOverride = (attributeCluster != nullptr) ? &reader : AttributeAccessInterfaceRegistry::Instance().Get(aPath.mEndpointId, aPath.mClusterId); if (attributeOverride) { bool triedEncode = false; ReturnErrorOnFailure(ReadViaAccessInterface(aSubjectDescriptor, aIsFabricFiltered, aPath, aAttributeReports, apEncoderState, attributeOverride, &triedEncode)); VerifyOrReturnError(!triedEncode, CHIP_NO_ERROR); } } // Read attribute using Ember, if it doesn't have an override. EmberAfAttributeSearchRecord record; record.endpoint = aPath.mEndpointId; record.clusterId = aPath.mClusterId; record.attributeId = aPath.mAttributeId; Status status = emAfReadOrWriteAttribute(&record, &attributeMetadata, gEmberAttributeIOBufferSpan.data(), static_cast(gEmberAttributeIOBufferSpan.size()), /* write = */ false); if (status != Status::Success) { return CHIP_ERROR_IM_GLOBAL_STATUS_VALUE(status); } // data available, return the corresponding record AttributeReportIB::Builder & attributeReport = aAttributeReports.CreateAttributeReport(); ReturnErrorOnFailure(aAttributeReports.GetError()); AttributeDataIB::Builder & attributeDataIBBuilder = attributeReport.CreateAttributeData(); ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); DataVersion version = 0; ReturnErrorOnFailure(ReadClusterDataVersion(aPath, version)); attributeDataIBBuilder.DataVersion(version); ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); AttributePathIB::Builder & attributePathIBBuilder = attributeDataIBBuilder.CreatePath(); ReturnErrorOnFailure(attributeDataIBBuilder.GetError()); CHIP_ERROR err = attributePathIBBuilder.Endpoint(aPath.mEndpointId) .Cluster(aPath.mClusterId) .Attribute(aPath.mAttributeId) .EndOfAttributePathIB(); ReturnErrorOnFailure(err); TLV::TLVWriter * writer = attributeDataIBBuilder.GetWriter(); VerifyOrReturnError(writer != nullptr, CHIP_NO_ERROR); const EmberAfAttributeType attributeType = attributeMetadata->attributeType; const bool isNullable = attributeMetadata->IsNullable(); const TLV::Tag tag = TLV::ContextTag(AttributeDataIB::Tag::kData); switch (AttributeBaseType(attributeType)) { case ZCL_NO_DATA_ATTRIBUTE_TYPE: // No data ReturnErrorOnFailure(writer->PutNull(tag)); break; case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer { using IntType = OddSizedInteger<3, false>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer { using IntType = OddSizedInteger<5, false>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer { using IntType = OddSizedInteger<6, false>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer { using IntType = OddSizedInteger<7, false>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer { using IntType = OddSizedInteger<3, true>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer { using IntType = OddSizedInteger<5, true>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer { using IntType = OddSizedInteger<6, true>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer { using IntType = OddSizedInteger<7, true>; ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; } case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float ReturnErrorOnFailure(attributeBufferToNumericTlvData(*writer, isNullable)); break; case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string { char * actualData = reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 1); uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { VerifyOrReturnError(isNullable, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(writer->PutNull(tag)); } else { ReturnErrorOnFailure(writer->PutString(tag, actualData, dataLength)); } break; } case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: { char * actualData = reinterpret_cast(gEmberAttributeIOBufferSpan.data() + 2); // The pascal string contains 2 bytes length uint16_t dataLength; memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { VerifyOrReturnError(isNullable, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(writer->PutNull(tag)); } else { ReturnErrorOnFailure(writer->PutString(tag, actualData, dataLength)); } break; } case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string { uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 1; uint8_t dataLength = gEmberAttributeIOBufferSpan[0]; if (dataLength == 0xFF) { VerifyOrReturnError(isNullable, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(writer->PutNull(tag)); } else { ReturnErrorOnFailure(writer->Put(tag, chip::ByteSpan(actualData, dataLength))); } break; } case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: { uint8_t * actualData = gEmberAttributeIOBufferSpan.data() + 2; // The pascal string contains 2 bytes length uint16_t dataLength; memcpy(&dataLength, gEmberAttributeIOBufferSpan.data(), sizeof(dataLength)); if (dataLength == 0xFFFF) { VerifyOrReturnError(isNullable, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(writer->PutNull(tag)); } else { ReturnErrorOnFailure(writer->Put(tag, chip::ByteSpan(actualData, dataLength))); } break; } default: ChipLogError(DataManagement, "Attribute type 0x%x not handled", static_cast(attributeType)); return CHIP_IM_GLOBAL_STATUS(Failure); } // If we got this far, placing the data to be read in the output TLVWriter succeeded. // Try to terminate our attribute report to signal success. ReturnErrorOnFailure(attributeDataIBBuilder.EndOfAttributeDataIB()); return attributeReport.EndOfAttributeReportIB(); } namespace { template CHIP_ERROR numericTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isNullable, uint16_t & dataLen) { typename NumericAttributeTraits::StorageType value; VerifyOrDie(sizeof(value) <= gEmberAttributeIOBufferSpan.size()); if (isNullable && aReader.GetType() == TLV::kTLVType_Null) { NumericAttributeTraits::SetNull(value); } else { typename NumericAttributeTraits::WorkingType val; ReturnErrorOnFailure(aReader.Get(val)); VerifyOrReturnError(NumericAttributeTraits::CanRepresentValue(isNullable, val), CHIP_ERROR_INVALID_ARGUMENT); NumericAttributeTraits::WorkingToStorage(val, value); } dataLen = sizeof(value); memcpy(gEmberAttributeIOBufferSpan.data(), &value, sizeof(value)); return CHIP_NO_ERROR; } template CHIP_ERROR stringTlvDataToAttributeBuffer(TLV::TLVReader & aReader, bool isOctetString, bool isNullable, uint16_t & dataLen) { const uint8_t * data = nullptr; T len; if (isNullable && aReader.GetType() == TLV::kTLVType_Null) { // Null is represented by an 0xFF or 0xFFFF length, respectively. len = std::numeric_limits::max(); memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); dataLen = sizeof(len); } else { VerifyOrReturnError((isOctetString && aReader.GetType() == TLV::TLVType::kTLVType_ByteString) || (!isOctetString && aReader.GetType() == TLV::TLVType::kTLVType_UTF8String), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(CanCastTo(aReader.GetLength()), CHIP_ERROR_MESSAGE_TOO_LONG); ReturnErrorOnFailure(aReader.GetDataPtr(data)); len = static_cast(aReader.GetLength()); VerifyOrReturnError(len != std::numeric_limits::max(), CHIP_ERROR_MESSAGE_TOO_LONG); VerifyOrReturnError(len + sizeof(len) /* length at the beginning of data */ <= gEmberAttributeIOBufferSpan.size(), CHIP_ERROR_MESSAGE_TOO_LONG); memcpy(gEmberAttributeIOBufferSpan.data(), &len, sizeof(len)); memcpy(gEmberAttributeIOBufferSpan.data() + sizeof(len), data, len); dataLen = static_cast(len + sizeof(len)); } return CHIP_NO_ERROR; } CHIP_ERROR prepareWriteData(const EmberAfAttributeMetadata * attributeMetadata, TLV::TLVReader & aReader, uint16_t & dataLen) { EmberAfAttributeType expectedType = AttributeBaseType(attributeMetadata->attributeType); bool isNullable = attributeMetadata->IsNullable(); switch (expectedType) { case ZCL_BOOLEAN_ATTRIBUTE_TYPE: // Boolean return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT8U_ATTRIBUTE_TYPE: // Unsigned 8-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT16U_ATTRIBUTE_TYPE: // Unsigned 16-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT24U_ATTRIBUTE_TYPE: // Unsigned 24-bit integer { using IntType = OddSizedInteger<3, false>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT32U_ATTRIBUTE_TYPE: // Unsigned 32-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT40U_ATTRIBUTE_TYPE: // Unsigned 40-bit integer { using IntType = OddSizedInteger<5, false>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT48U_ATTRIBUTE_TYPE: // Unsigned 48-bit integer { using IntType = OddSizedInteger<6, false>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT56U_ATTRIBUTE_TYPE: // Unsigned 56-bit integer { using IntType = OddSizedInteger<7, false>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT64U_ATTRIBUTE_TYPE: // Unsigned 64-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT8S_ATTRIBUTE_TYPE: // Signed 8-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT16S_ATTRIBUTE_TYPE: // Signed 16-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT24S_ATTRIBUTE_TYPE: // Signed 24-bit integer { using IntType = OddSizedInteger<3, true>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT32S_ATTRIBUTE_TYPE: // Signed 32-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_INT40S_ATTRIBUTE_TYPE: // Signed 40-bit integer { using IntType = OddSizedInteger<5, true>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT48S_ATTRIBUTE_TYPE: // Signed 48-bit integer { using IntType = OddSizedInteger<6, true>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT56S_ATTRIBUTE_TYPE: // Signed 56-bit integer { using IntType = OddSizedInteger<7, true>; return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); } case ZCL_INT64S_ATTRIBUTE_TYPE: // Signed 64-bit integer return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_SINGLE_ATTRIBUTE_TYPE: // 32-bit float return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_DOUBLE_ATTRIBUTE_TYPE: // 64-bit float return numericTlvDataToAttributeBuffer(aReader, isNullable, dataLen); case ZCL_OCTET_STRING_ATTRIBUTE_TYPE: // Octet string case ZCL_CHAR_STRING_ATTRIBUTE_TYPE: // Char string return stringTlvDataToAttributeBuffer(aReader, expectedType == ZCL_OCTET_STRING_ATTRIBUTE_TYPE, isNullable, dataLen); case ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE: // Long octet string case ZCL_LONG_CHAR_STRING_ATTRIBUTE_TYPE: // Long char string return stringTlvDataToAttributeBuffer(aReader, expectedType == ZCL_LONG_OCTET_STRING_ATTRIBUTE_TYPE, isNullable, dataLen); default: ChipLogError(DataManagement, "Attribute type %x not handled", static_cast(expectedType)); return CHIP_ERROR_INVALID_DATA_LIST; } } } // namespace const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath) { return emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); } CHIP_ERROR WriteSingleClusterData(const SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, TLV::TLVReader & aReader, WriteHandler * apWriteHandler) { // Check attribute existence. This includes attributes with registered metadata, but also specially handled // mandatory global attributes (which just check for cluster on endpoint). const EmberAfCluster * attributeCluster = nullptr; const EmberAfAttributeMetadata * attributeMetadata = nullptr; FindAttributeMetadata(aPath, &attributeCluster, &attributeMetadata); if (attributeCluster == nullptr && attributeMetadata == nullptr) { return apWriteHandler->AddStatus(aPath, UnsupportedAttributeStatus(aPath)); } // All the global attributes we don't have metadata for are readonly. if (attributeMetadata == nullptr || attributeMetadata->IsReadOnly()) { return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::UnsupportedWrite); } { Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId, .requestType = Access::RequestType::kAttributeWriteRequest, .entityId = aPath.mAttributeId }; Access::Privilege requestPrivilege = RequiredPrivilege::ForWriteAttribute(aPath); CHIP_ERROR err = CHIP_NO_ERROR; if (!apWriteHandler->ACLCheckCacheHit({ aPath, requestPrivilege })) { err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, requestPrivilege); } if (err != CHIP_NO_ERROR) { VerifyOrReturnError((err == CHIP_ERROR_ACCESS_DENIED) || (err == CHIP_ERROR_ACCESS_RESTRICTED_BY_ARL), err); // TODO: when wildcard/group writes are supported, handle them to discard rather than fail with status return apWriteHandler->AddStatus(aPath, err == CHIP_ERROR_ACCESS_DENIED ? Protocols::InteractionModel::Status::UnsupportedAccess : Protocols::InteractionModel::Status::AccessRestricted); } apWriteHandler->CacheACLCheckResult({ aPath, requestPrivilege }); } if (attributeMetadata->MustUseTimedWrite() && !apWriteHandler->IsTimedWrite()) { return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::NeedsTimedInteraction); } if (aPath.mDataVersion.HasValue() && !IsClusterDataVersionEqual(aPath, aPath.mDataVersion.Value())) { ChipLogError(DataManagement, "Write Version mismatch for Endpoint %x, Cluster " ChipLogFormatMEI, aPath.mEndpointId, ChipLogValueMEI(aPath.mClusterId)); return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::DataVersionMismatch); } if (auto * attrOverride = AttributeAccessInterfaceRegistry::Instance().Get(aPath.mEndpointId, aPath.mClusterId)) { AttributeValueDecoder valueDecoder(aReader, aSubjectDescriptor); ReturnErrorOnFailure(attrOverride->Write(aPath, valueDecoder)); if (valueDecoder.TriedDecode()) { MatterReportingAttributeChangeCallback(aPath); return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::Success); } } CHIP_ERROR preparationError = CHIP_NO_ERROR; uint16_t dataLen = 0; if ((preparationError = prepareWriteData(attributeMetadata, aReader, dataLen)) != CHIP_NO_ERROR) { ChipLogDetail(Zcl, "Failed to prepare data to write: %" CHIP_ERROR_FORMAT, preparationError.Format()); return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::InvalidValue); } if (dataLen > attributeMetadata->size) { ChipLogDetail(Zcl, "Data to write exceedes the attribute size claimed."); return apWriteHandler->AddStatus(aPath, Protocols::InteractionModel::Status::InvalidValue); } auto status = emAfWriteAttributeExternal( aPath, EmberAfWriteDataInput(gEmberAttributeIOBufferSpan.data(), attributeMetadata->attributeType)); return apWriteHandler->AddStatus(aPath, status); } bool IsClusterDataVersionEqual(const ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion) { DataVersion * version = emberAfDataVersionStorage(aConcreteClusterPath); if (version == nullptr) { ChipLogError(DataManagement, "Endpoint %x, Cluster " ChipLogFormatMEI " not found in IsClusterDataVersionEqual!", aConcreteClusterPath.mEndpointId, ChipLogValueMEI(aConcreteClusterPath.mClusterId)); return false; } return (*(version)) == aRequiredVersion; } bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint) { CHIP_ERROR err; auto deviceTypeList = emberAfDeviceTypeListFromEndpoint(endpoint, err); if (err != CHIP_NO_ERROR) { return false; } for (auto & device : deviceTypeList) { if (device.deviceId == deviceType) { return true; } } return false; } Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath) { using Protocols::InteractionModel::Status; const EmberAfEndpointType * type = emberAfFindEndpointType(aPath.mEndpointId); if (type == nullptr) { return Status::UnsupportedEndpoint; } const EmberAfCluster * cluster = emberAfFindClusterInType(type, aPath.mClusterId, CLUSTER_MASK_SERVER); if (cluster == nullptr) { return Status::UnsupportedCluster; } // No way to tell. Just claim supported. return Status::Success; } } // namespace app } // namespace chip